Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
18805 lines (18156 sloc) 679 KB
/// automated tests for common units of the Synopse mORMot Framework
// - this unit is a part of the freeware Synopse mORMot framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynSelfTests;
{
This file is part of Synopse mORMot framework.
Synopse framework. Copyright (C) 2018 Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is Synopse framework.
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (C) 2018
the Initial Developer. All Rights Reserved.
Contributor(s):
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****
Version 1.8
- first public release, corresponding to SQLite3 Framework 1.8
- includes Unitary Testing class and functions
Version 1.9
- test multi-threaded AES encryption/decryption of 4 MB blocks
- added crc32 tests
Version 1.11
- added some more regression tests in TTestCompression.ZipFormat
Version 1.12
- handle producer version change in TTestSynopsePDF
Version 1.13
- code modifications to compile with Delphi 5 compiler
- enhanced compression tests
Version 1.15
- unit now tested with Delphi XE2 (32 Bit)
- new TTestSQLite3ExternalDB class to test TSQLRecordExternal records,
i.e. external DB access from the mORMot framework (use an in-memory SQLite3
database as an external SynDB engine for fast and reliable testing)
- added test of TModTime published property (i.e. latest update time)
- added test of TCreateTime published property (i.e. record creation time)
Version 1.16
- added interface-based remote service implementation tests
- added test about per-database encryption in TTestExternalDatabase.CryptedDatabase
- added TAESECB, TAESCBC, TAESCFB, TAESOFB and TAESCTR classes tests (+ PKCS7)
- enhanced SynLZ tests (comparing asm and pas versions of the implementation)
Version 1.17
- added test for TInterfaceCollection kind of parameter
- added multi-thread testing of ExecuteInMainThread() method
- removed TSQLRecordExternal class type, to allow any TSQLRecord (e.g.
TSQLRecordMany) to be used with VirtualTableExternalRegister()
- added DBMS full test coverage in TTestExternalDatabase.AutoAdaptSQL
Version 1.18
- included some unit tests like TTestLowLevelTypes and TTestBasicClasses,
previously included in SQLite3Commons.pas or TTestLowLevelCommon, extracted
from SynCommons.pas, or TTestSQLite3Engine from SQLite3.pas
- added test for variant JSON serialization for interface-based services and
for ORM (aka TSQLRecord)
- added test for SynLZdecompress1partial() new function
- added external TSQLRecordOnlyBlob test associated to ticket [21c2d5ae96]
and OleDB/JET-based external database tests
- included testing of interface-based services in sicSingle, sicPerSession,
sicPerUser, sicPerGroup and sicPerThread modes
- included testing of ServiceContext threadvar for opt*InMainThread or
opt*InPerInterfaceThread options
- included testing of new REGEXP function for SQLite3
- included testing of TSQLRestServerAuthenticationNone
- added TTestMultiThreadProcess test cases over all communication protocols
- introducing TTestDDDSharedUnits test cases
- added PDF-1.5 and page orientation testing
- now default HTTP port would be 8888 under Linux (888 needs root rights)
}
interface
{$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64
{$ifdef ISDELPHIXE}
// since Delphi XE, we have unit System.RegularExpressionsAPI available
{$define TEST_REGEXP}
{$else}
// define only if you have unit PCRE.pas installed (not set by default)
{.$define TEST_REGEXP}
{$endif}
uses
{$ifdef MSWINDOWS}
Windows,
{$else}
{$ifdef KYLIX3}
Types,
LibC,
SynKylix,
{$endif}
{$ifdef FPC}
SynFPCLinux,
BaseUnix,
{$endif}
{$endif}
Classes,
SynCrtSock,
SynTable, // for TSynTableStatement
{$ifndef NOVARIANTS}
SynMongoDB,
SynMustache,
Variants,
{$endif}
{$ifdef UNICODE}
Generics.Collections,
{$endif}
SysUtils,
{$ifndef LVCL}
Contnrs,
{$ifdef MSWINDOWS}
SynOleDB,
{$ifndef FPC}
SynGdiPlus,
SynPdf,
{$endif}
{$endif}
{$endif LVCL}
SynEcc,
SynDB,
SynSQLite3,
SynSQLite3Static,
SynDBSQLite3,
SynDBRemote,
SynDBODBC,
{$ifndef DELPHI5OROLDER}
mORMot,
mORMotDB,
mORMotSQLite3,
mORMotHttpServer,
mORMotHttpClient,
{$ifndef NOVARIANTS}
mORMotMongoDB,
mORMotMVC,
{$endif}
SynBidirSock,
mORMotDDD,
dddDomUserTypes,
dddDomUserInterfaces,
dddDomAuthInterfaces,
dddInfraEmail,
dddInfraEmailer,
dddInfraAuthRest,
dddInfraRepoUser,
ECCProcess,
{$endif DELPHI5OROLDER}
SynProtoRTSPHTTP,
{$ifdef TEST_REGEXP}
SynSQLite3RegEx,
{$endif TEST_REGEXP}
{$ifdef MSWINDOWS}
{$ifdef USEZEOS}
SynDBZeos,
{$endif}
{$endif}
SynCommons,
SynLog,
SynTests;
{ ************ Unit-Testing classes and functions }
{$ifndef DELPHI5OROLDER}
const
{$ifdef MSWINDOWS}
HTTP_DEFAULTPORT = '888';
// if this library file is available and USEZEOS conditional is set, will run
// TTestExternalDatabase.FirebirdEmbeddedViaODBC
// !! download driver from http://www.firebirdsql.org/en/odbc-driver
FIREBIRDEMBEDDEDDLL = 'd:\Dev\Lib\SQLite3\Samples\15 - External DB performance\Firebird'+
{$ifdef CPU64}'64'+{$endif=}'\fbembed.dll';
{$else}
HTTP_DEFAULTPORT = '8888'; // under Linux, port<1024 needs root user
{$endif MSWINDOWS}
type
// a record mapping used in the test classes of the framework
// - this class can be used for debugging purposes, with the database
// created by TTestFileBased in mORMotSQLite3.pas
// - this class will use 'People' as a table name
TSQLRecordPeople = class(TSQLRecord)
private
fData: TSQLRawBlob;
fFirstName: RawUTF8;
fLastName: RawUTF8;
fYearOfBirth: integer;
fYearOfDeath: word;
published
property FirstName: RawUTF8 read fFirstName write fFirstName;
property LastName: RawUTF8 read fLastName write fLastName;
property Data: TSQLRawBlob read fData write fData;
property YearOfBirth: integer read fYearOfBirth write fYearOfBirth;
property YearOfDeath: word read fYearOfDeath write fYearOfDeath;
public
/// method used to test the Client-Side
// ModelRoot/TableName/ID/MethodName RESTful request, i.e.
// ModelRoot/People/ID/DataAsHex in this case
// - this method calls the supplied TSQLRestClient to retrieve its results,
// with the ID taken from the current TSQLRecordPeole instance ID field
// - parameters and result types depends on the purpose of the function
// - TSQLRestServerTest.DataAsHex published method implements the result
// calculation on the Server-Side
function DataAsHex(aClient: TSQLRestClientURI): RawUTF8;
/// method used to test the Client-Side
// ModelRoot/MethodName RESTful request, i.e. ModelRoot/Sum in this case
// - this method calls the supplied TSQLRestClient to retrieve its results
// - parameters and result types depends on the purpose of the function
// - TSQLRestServerTest.Sum published method implements the result calculation
// on the Server-Side
// - this method doesn't expect any ID to be supplied, therefore will be
// called as class function - normally, it should be implement in a
// TSQLRestClient descendant, and not as a TSQLRecord, since it does't depend
// on TSQLRecordPeople at all
// - you could also call the same servce from the ModelRoot/People/ID/Sum URL,
// but it won't make any difference)
class function Sum(aClient: TSQLRestClientURI; a, b: double; Method2: boolean): double;
end;
TSQLRecordTest = class(TSQLRecord)
private
fTest: RawUTF8;
fValfloat: double;
fValWord: word;
fNext: TSQLRecordTest;
fInt: int64;
fValDate: TDateTime;
fData: TSQLRawBlob;
fAnsi: WinAnsiString;
fUnicode: RawUnicode;
{$ifndef NOVARIANTS}
fVariant: variant;
{$endif}
procedure SetInt(const Value: int64);
public
procedure FillWith(i: Integer);
procedure CheckWith(test: TSynTestCase; i: Integer; offset: integer=0;
checkblob: boolean=true);
published
property Int: int64 read fInt write SetInt default 12;
property Test: RawUTF8 read fTest write fTest;
property Unicode: RawUnicode read fUnicode write fUnicode;
property Ansi: WinAnsiString read fAnsi write fAnsi;
property ValFloat: double read fValfloat write fValFloat;
property ValWord: word read fValWord write fValWord;
property ValDate: tdatetime read fValDate write fValDate;
property Next: TSQLRecordTest read fNext write fNext;
property Data: TSQLRawBlob read fData write fData;
{$ifndef NOVARIANTS}
property ValVariant: variant read fVariant write fVariant;
{$endif}
end;
{$endif}
type
/// this test case will test most functions, classes and types defined and
// implemented in the SynCommons unit
TTestLowLevelCommon = class(TSynTestCase)
protected
{$ifndef DELPHI5OROLDER}
da: IObjectDynArray; // force the interface to be defined BEFORE the array
a: array of TSQLRecordPeople;
{$endif}
fAdd,fDel: RawUTF8;
fQuickSelectValues: TIntegerDynArray;
function QuickSelectGT(IndexA,IndexB: PtrInt): boolean;
procedure intadd(const Sender; Value: integer);
procedure intdel(const Sender; Value: integer);
published
/// the faster CopyRecord function, enhancing the system.pas unit
procedure SystemCopyRecord;
/// test the TRawUTF8List class
procedure _TRawUTF8List;
/// test the TDynArray object and methods
procedure _TDynArray;
/// test the TDynArrayHashed object and methods (dictionary features)
// - this test will create an array of 200,000 items to test speed
procedure _TDynArrayHashed;
/// test TObjectListHashed class
procedure _TObjectListHashed;
/// test TObjectListSorted class
procedure _TObjectListSorted;
/// test TSynNameValue class
procedure _TSynNameValue;
/// test TRawUTF8Interning process
procedure _TRawUTF8Interning;
{$ifndef DELPHI5OROLDER}
/// test TObjectDynArrayWrapper class
procedure _TObjectDynArrayWrapper;
/// test T*ObjArray types and the ObjArray*() wrappers
procedure _TObjArray;
{$endif}
/// test StrIComp() and AnsiIComp() functions
procedure FastStringCompare;
/// test IdemPropName() and IdemPropNameU() functions
procedure _IdemPropName;
/// test UrlEncode() and UrlDecode() functions
procedure UrlEncoding;
/// test our internal fast TGUID process functions
procedure _GUID;
/// test IsMatch() function
procedure _IsMatch;
/// the Soundex search feature (i.e. TSynSoundex and all related
// functions)
procedure Soundex;
/// low level fast Integer or Floating-Point to/from string conversion
// - especially the RawUTF8 or PUTF8Char relative versions
procedure NumericalConversions;
/// test low-level integer/Int64 functions
procedure Integers;
/// test crc32c in both software and hardware (SSE4.2) implementations
procedure _crc32c;
/// test RDRAND Intel x86/x64 opcode if available, or fast gsl_rng_taus2
procedure _Random32;
/// test TSynBloomFilter class
procedure BloomFilters;
/// test DeltaCompress/DeltaExtract functions
procedure _DeltaCompress;
/// the new fast Currency to/from string conversion
procedure Curr64;
/// the camel-case / camel-uncase features, used for i18n from Delphi RTII
procedure _CamelCase;
/// the low-level bit management functions
procedure Bits;
/// the fast .ini file content direct access
procedure IniFiles;
/// test UTF-8 and Win-Ansi conversion (from or to, through RawUnicode)
procedure _UTF8;
/// test ASCII Baudot encoding
procedure BaudotCode;
/// the ISO-8601 date and time encoding
// - test especially the conversion to/from text
procedure Iso8601DateAndTime;
/// test the TSynTimeZone class and its cross-platform local time process
procedure TimeZones;
/// test UrlEncode() and UrlDecode() functions
// - this method use some ISO-8601 encoded dates and times for the testing
procedure UrlDecoding;
/// test mime types recognition
procedure MimeTypes;
/// validates the median computation using the "Quick Select" algorithm
procedure QuickSelect;
/// test TSynTable class and TSynTableVariantType new variant type
procedure _TSynTable;
/// test the TSynCache class
procedure _TSynCache;
/// low-level TSynFilter classes
procedure _TSynFilter;
/// low-level TSynValidate classes
procedure _TSynValidate;
/// low-level TSynLogFile class
procedure _TSynLogFile;
/// client side geniune 64 bit identifiers generation
procedure _TSynUniqueIdentifier;
/// test the TSynDictionary class
procedure _TSynDictionary;
/// validate the TSynQueue class
procedure _TSynQueue;
end;
/// this test case will test most low-level functions, classes and types
// defined and implemented in the mORMot.pas unit
TTestLowLevelTypes = class(TSynTestCase)
{$ifndef NOVARIANTS}
protected
procedure MustacheTranslate(var English: string);
{$endif}
published
{$ifndef DELPHI5OROLDER}
/// some low-level RTTI access
// - especially the field type retrieval from published properties
procedure RTTI;
{$endif}
/// some low-level Url encoding from parameters
procedure UrlEncoding;
/// some low-level JSON encoding/decoding
procedure EncodeDecodeJSON;
{$ifndef NOVARIANTS}
/// some low-level variant process
procedure Variants;
/// test the Mustache template rendering unit
procedure MustacheRenderer;
{$ifndef DELPHI5OROLDER}
{$ifndef LVCL}
/// variant-based JSON/BSON document process
procedure _TDocVariant;
/// low-level TDecimal128 decimal value process (as used in BSON)
procedure _TDecimal128;
/// BSON process (using TDocVariant)
procedure _BSON;
{$endif LVCL}
/// test SELECT statement parsing
procedure _TSynTableStatement;
/// test advanced statistics monitoring
procedure _TSynMonitorUsage;
{$endif DELPHI5OROLDER}
{$endif NOVARIANTS}
end;
{$ifndef DELPHI5OROLDER}
/// this test case will test some generic classes
// defined and implemented in the mORMot.pas unit
TTestBasicClasses = class(TSynTestCase)
published
/// test the TSQLRecord class
// - especially SQL auto generation, or JSON export/import
procedure _TSQLRecord;
/// test the digital signature of records
procedure _TSQLRecordSigned;
/// test the TSQLModel class
procedure _TSQLModel;
/// test a full in-memory server over Windows Messages
// - Under Linux, URIDll will be used instead due to lack of message loop
// - without any SQLite3 engine linked
procedure _TSQLRestServerFullMemory;
end;
{$endif DELPHI5OROLDER}
/// this test case will test most functions, classes and types defined and
// implemented in the SynZip unit
TTestCompression = class(TSynTestCase)
protected
Data: RawByteString;
M: THeapMemoryStream;
crc0,crc1: cardinal;
public
procedure Setup; override;
procedure CleanUp; override;
published
/// direct deflate/inflate functions
procedure InMemoryCompression;
/// .gzip archive handling
procedure GZIPFormat;
/// .zip archive handling
procedure ZIPFormat;
/// SynLZO internal format
procedure _SynLZO;
/// SynLZ internal format
procedure _SynLZ;
/// TAlgoCompress classes
procedure _TAlgoCompress;
end;
/// this test case will test most functions, classes and types defined and
// implemented in the SynCrypto unit
TTestCryptographicRoutines = class(TSynTestCase)
public
procedure CryptData(dpapi: boolean);
published
/// Adler32 hashing functions
procedure _Adler32;
/// MD5 hashing functions
procedure _MD5;
/// SHA-1 hashing functions
procedure _SHA1;
/// SHA-256 hashing functions
procedure _SHA256;
/// SHA-512 hashing functions
procedure _SHA512;
/// SHA-3 / Keccak hashing functions
procedure _SHA3;
/// AES encryption/decryption functions
procedure _AES256;
/// RC4 encryption function
procedure _RC4;
/// Base-64 encoding/decoding functions
procedure _Base64;
/// CompressShaAes() using SHA-256 / AES-256-CTR algorithm over SynLZ
procedure _CompressShaAes;
/// AES-based pseudorandom number generator
procedure _TAESPNRG;
/// CryptDataForCurrentUser() function
procedure _CryptDataForCurrentUser;
{$ifdef MSWINDOWS}
/// CryptDataForCurrentUserAPI() function
procedure _CryptDataForCurrentUserAPI;
{$endif MSWINDOWS}
{$ifndef NOVARIANTS}
/// JWT classes
procedure _JWT;
{$endif NOVARIANTS}
/// compute some performance numbers, mostly against regression
procedure Benchmark;
end;
/// this test case will test ECDH and ECDSA cryptography as implemented
// in the SynECC unit
TTestECCCryptography = class(TSynTestCase)
protected
pub: array of TECCPublicKey;
priv: array of TECCPrivateKey;
sign: array of TECCSignature;
hash: TECCHash;
published
/// avoid regression among platforms and compilers
procedure ReferenceVectors;
/// ECC private/public keys generation
procedure _ecc_make_key;
/// ECDSA signature computation
procedure _ecdsa_sign;
/// ECDSA signature verification
procedure _ecdsa_verify;
/// ECDH key derivation
procedure _ecdh_shared_secret;
/// ECDSA certificates chains and digital signatures
procedure CertificatesAndSignatures;
{$ifndef DELPHI5OROLDER}
/// run most commands of the ECC tool
procedure ECCCommandLineTool;
{$endif}
/// ECDHE stream protocol
procedure ECDHEStreamProtocol;
end;
/// this test case will validate several low-level protocols
TTestProtocols = class(TSynTestCase)
published
/// RTSP over HTTP, as implemented in SynProtoRTSPHTTP unit
procedure RTSPOverHTTP;
end;
{$ifdef MSWINDOWS}
{$ifndef LVCL}
{$ifndef FPC}
/// this test case will test most functions, classes and types defined and
// implemented in the SynPDF unit
TTestSynopsePDF = class(TSynTestCase)
published
/// create a PDF document, using the PDF Canvas property
// - test font handling, especially standard font substitution
procedure _TPdfDocument;
/// create a PDF document, using a EMF content
// - validates the EMF/TMetaFile enumeration, and its conversion into the
// PDF content, including PDF-1.5 and page orientation
// - this method will produce a .pdf file in the executable directory,
// if you want to check out the result (it's simply a curve drawing, with
// data from NIST)
procedure _TPdfDocumentGDI;
end;
{$endif}
{$endif}
{$endif}
{$ifndef DELPHI5OROLDER}
{$ifndef LVCL}
type
TCollTest = class(TCollectionItem)
private
FLength: Integer;
FColor: Integer;
FName: RawUTF8;
published
property Color: Integer read FColor write FColor;
property Length: Integer read FLength write FLength;
property Name: RawUTF8 read FName write FName;
end;
TCollTestsI = class(TInterfacedCollection)
protected
class function GetClass: TCollectionItemClass; override;
end;
{$endif LVCL}
type
/// a parent test case which will test most functions, classes and types defined
// and implemented in the mORMotSQLite3 unit, i.e. the SQLite3 engine itself
// - it should not be called directly, but through TTestFileBased,
// TTestMemoryBased and TTestMemoryBased children
TTestSQLite3Engine = class(TSynTestCase)
protected
{ these values are used internaly by the published methods below }
BackupProgressStep: TSQLDatabaseBackupEventStep; // should be the first
TempFileName: TFileName;
EncryptedFile: boolean;
Demo: TSQLDataBase;
Req: RawUTF8;
JS: RawUTF8;
BackupTimer: TPrecisionTimer;
function OnBackupProgress(Sender: TSQLDatabaseBackupThread): Boolean;
published
/// test direct access to the SQLite3 engine
// - i.e. via TSQLDataBase and TSQLRequest classes
procedure DatabaseDirectAccess;
/// test direct access to the Virtual Table features of SQLite3
procedure VirtualTableDirectAccess;
/// test the TSQLTableJSON table
// - the JSON content generated must match the original data
// - a VACCUM is performed, for testing some low-level SQLite3 engine
// implementation
// - the SortField feature is also tested
procedure _TSQLTableJSON;
/// test the TSQLRestClientDB, i.e. a local Client/Server driven usage
// of the framework
// - validates TSQLModel, TSQLRestServer and TSQLRestStorage by checking
// the coherency of the data between client and server instances, after
// update from both sides
// - use all RESTful commands (GET/UDPATE/POST/DELETE...)
// - test the 'many to many' features (i.e. TSQLRecordMany) and dynamic
// arrays published properties handling
// - test dynamic tables
procedure _TSQLRestClientDB;
{$ifdef TEST_REGEXP}
/// check the PCRE-based REGEX function
procedure RegexpFunction;
{$endif TEST_REGEXP}
/// test Master/Slave replication using TRecordVersion field
procedure _TRecordVersion;
end;
/// this test case will test most functions, classes and types defined and
// implemented in the mORMotSQLite3 unit, i.e. the SQLite3 engine itself,
// with a file-based approach
TTestFileBased = class(TTestSQLite3Engine);
/// this test case will test most functions, classes and types defined and
// implemented in the mORMotSQLite3 unit, i.e. the SQLite3 engine itself,
// with a memory-based approach
// - this class will also test the TSQLRestStorage class, and its
// 100% Delphi simple database engine
TTestMemoryBased = class(TTestSQLite3Engine)
protected
function CreateShardDB(maxshard: Integer): TSQLRestServer;
published
/// test the TSQLTableWritable table
procedure _TSQLTableWritable;
/// validate RTREE virtual tables
procedure _RTree;
/// validate TSQLRestStorageShardDB add operation, with or without batch
procedure ShardWrite;
/// validate TSQLRestStorageShardDB reading among all sharded databases
procedure ShardRead;
/// validate TSQLRestStorageShardDB reading after deletion of several shards
procedure ShardReadAfterPurge;
/// validate TSQLRestStorageShardDB.MaxShardCount implementation
procedure _MaxShardCount;
end;
/// this test case will test most functions, classes and types defined and
// implemented in the mORMotSQLite3 unit, i.e. the SQLite3 engine itself,
// with a file-based approach
// - purpose of this class is to test Write-Ahead Logging for the database
TTestFileBasedWAL = class(TTestFileBased);
/// this test case will test most functions, classes and types defined and
// implemented in the mORMotSQLite3 unit, i.e. the SQLite3 engine itself,
// with a file-based approach
// - purpose of this class is to test Memory-Mapped I/O for the database
TTestFileBasedMemoryMap = class(TTestFileBased);
/// this test case will test most functions, classes and types defined and
// implemented in the mORMotSQLite3 unit, i.e. the SQLite3 engine itself,
// used as a HTTP/1.1 server and client
// - test a HTTP/1.1 server and client on the port 888 of the local machine
// - require the 'test.db3' SQLite3 database file, as created by TTestFileBased
TTestClientServerAccess = class(TSynTestCase)
protected
{ these values are used internaly by the published methods below }
Model: TSQLModel;
DataBase: TSQLRestServerDB;
Server: TSQLHttpServer;
Client: TSQLRestClientURI;
/// perform the tests of the current Client instance
procedure ClientTest;
/// release used instances (e.g. http server) and memory
procedure CleanUp; override;
public
/// this could be called as administrator for THttpApiServer to work
{$ifdef MSWINDOWS}
class function RegisterAddUrl(OnlyDelete: boolean): string;
{$endif}
published
/// initialize a TSQLHttpServer instance
// - uses the 'test.db3' SQLite3 database file generated by TTestSQLite3Engine
// - creates and validates a HTTP/1.1 server on the port 888 of the local
// machine, using the THttpApiServer (using kernel mode http.sys) class
// if available
procedure _TSQLHttpServer;
/// validate the HTTP/1.1 client implementation
// - by using a request of all records data
procedure _TSQLHttpClient;
/// validate the HTTP/1.1 client multi-query implementation with one
// connection for the all queries
// - this method keep alive the HTTP connection, so is somewhat faster
// - it runs 1000 remote SQL queries, and check the JSON data retrieved
// - the time elapsed for this step is computed, and displayed on the report
procedure HTTPClientKeepAlive;
/// validate the HTTP/1.1 client multi-query implementation with one
// connection initialized per query
// - this method don't keep alive the HTTP connection, so is somewhat slower:
// a new HTTP connection is created for every query
// - it runs 1000 remote SQL queries, and check the JSON data retrieved
// - the time elapsed for this step is computed, and displayed on the report
procedure HTTPClientMultiConnect;
/// validate the HTTP/1.1 client multi-query implementation with one
// connection for the all queries and our proprietary SHA-256 / AES-256-CTR
// encryption encoding
// - it runs 1000 remote SQL queries, and check the JSON data retrieved
// - the time elapsed for this step is computed, and displayed on the report
procedure HTTPClientEncrypted;
/// validates TSQLRest.SetCustomEncryption process with AES+SHA
procedure HTTPClientCustomEncryptionAesSha;
/// validates TSQLRest.SetCustomEncryption process with only AES
procedure HTTPClientCustomEncryptionAes;
/// validates TSQLRest.SetCustomEncryption process with only SHA
procedure HTTPClientCustomEncryptionSha;
{
/// validate the HTTP/1.1 client multi-query implementation with one
// connection for all queries, and the THttpServer class instead
// of the THttpApiServer kernel mode server
procedure HTTPClientKeepAliveDelphi;
/// validate the HTTP/1.1 client multi-query implementation with one
// connection initialized per query, and the THttpServer class instead
// of the THttpApiServer kernel mode server
// - this method don't keep alive the HTTP connection, so is somewhat slower:
// a new HTTP connection is created for every query
procedure HTTPClientMultiConnectDelphi;
}
{$ifdef MSWINDOWS}
/// validate the Named-Pipe client implementation
// - it first launch the Server as Named-Pipe
// - it then runs 1000 remote SQL queries, and check the JSON data retrieved
// - the time elapsed for this step is computed, and displayed on the report
procedure NamedPipeAccess;
/// validate the Windows Windows Messages based client implementation
// - it first launch the Server to handle Windows Messages
// - it then runs 1000 remote SQL queries, and check the JSON data retrieved
// - the time elapsed for this step is computed, and displayed on the report
procedure LocalWindowMessages;
/// validate the client implementation, using direct access to the server
// - it connects directly the client to the server, therefore use the same
// process and memory during the run: it's the fastest possible way of
// communicating
// - it then runs 1000 remote SQL queries, and check the JSON data retrieved
// - the time elapsed for this step is computed, and displayed on the report
{$endif}
procedure DirectInProcessAccess;
/// validate HTTP/1.1 client-server with multiple TSQLRestServer instances
procedure HTTPSeveralDBServers;
end;
/// this class defined two published methods of type TSQLRestServerCallBack in
// order to test the Server-Side ModelRoot/TableName/ID/MethodName RESTful model
TSQLRestServerTest = class(TSQLRestServerDB)
published
/// test ModelRoot/People/ID/DataAsHex
// - this method is called by TSQLRestServer.URI when a
// ModelRoot/People/ID/DataAsHex GET request is provided
// - Parameters values are not used here: this service only need aRecord.ID
// - SentData is set with incoming data from a PUT method
// - if called from ModelRoot/People/ID/DataAsHex with GET or PUT methods,
// TSQLRestServer.URI will create a TSQLRecord instance and set its ID
// (but won't retrieve its other field values automaticaly)
// - if called from ModelRoot/People/DataAsHex with GET or PUT methods,
// TSQLRestServer.URI will leave aRecord.ID=0 before launching it
// - if called from ModelRoot/DataAsHex with GET or PUT methods,
// TSQLRestServer.URI will leave aRecord=nil before launching it
// - implementation must return the HTTP error code (e.g. 200 as success)
// - Table is overloaded as TSQLRecordPeople here, and still match the
// TSQLRestServerCallBack prototype: but you have to check the class
// at runtime: it can be called by another similar but invalid URL, like
// ModelRoot/OtherTableName/ID/DataAsHex
procedure DataAsHex(Ctxt: TSQLRestServerURIContext);
/// method used to test the Server-Side ModelRoot/Sum or
// ModelRoot/People/Sum Requests with JSON process
// - implementation of this method returns the sum of two floating-points,
// named A and B, as in the public TSQLRecordPeople.Sum() method,
// which implements the Client-Side of this service
// - Table nor ID are never used here
procedure Sum(Ctxt: TSQLRestServerURIContext);
/// method used to test the Server-Side ModelRoot/Sum or
// ModelRoot/People/Sum Requests with variant process
procedure Sum2(Ctxt: TSQLRestServerURIContext);
end;
/// a test case which will test most external DB functions of the mORMotDB unit
// - the external DB will be in fact a SynDBSQLite3 instance, expecting a
// test.db3 SQlite3 file available in the current directory, populated with
// some TSQLRecordPeople rows
// - note that SQL statement caching at SQLite3 engine level makes those test
// 2 times faster: nice proof of performance improvement
TTestExternalDatabase = class(TSynTestCase)
protected
fExternalModel: TSQLModel;
fPeopleData: TSQLTable;
/// called by ExternalViaREST/ExternalViaVirtualTable and
// ExternalViaRESTWithChangeTracking tests method
procedure Test(StaticVirtualTableDirect, TrackChanges: boolean);
public
/// release used instances (e.g. server) and memory
procedure CleanUp; override;
published
{$ifndef LVCL}
/// test TQuery emulation class
procedure _TQuery;
{$endif}
/// test SynDB connection remote access via HTTP
procedure _SynDBRemote;
/// test TSQLDBConnectionProperties persistent as JSON
procedure DBPropertiesPersistence;
/// initialize needed RESTful client (and server) instances
// - i.e. a RESTful direct access to an external DB
procedure ExternalRecords;
/// check the SQL auto-adaptation features
procedure AutoAdaptSQL;
/// check the per-db encryption
// - the testpass.db3-wal file is not encrypted, but the main
// testpass.db3 file will
procedure CryptedDatabase;
/// test external DB implementation via faster REST calls
// - will mostly call directly the TSQLRestStorageExternal instance,
// bypassing the Virtual Table mechanism of SQLite3
procedure ExternalViaREST;
/// test external DB implementation via slower Virtual Table calls
// - using the Virtual Table mechanism of SQLite3 is more than 2 times
// slower than direct REST access
procedure ExternalViaVirtualTable;
/// test external DB implementation via faster REST calls and change tracking
// - a TSQLRecordHistory table will be used to store record history
procedure ExternalViaRESTWithChangeTracking;
{$ifndef CPU64}
{$ifndef LVCL}
{$ifdef MSWINDOWS}
/// test external DB using the JET engine
procedure JETDatabase;
{$endif}
{$endif}
{$endif}
{$ifdef MSWINDOWS}
{$ifdef USEZEOS}
/// test external Firebird embedded engine via Zeos/ZDBC (if available)
procedure FirebirdEmbeddedViaZDBCOverHTTP;
{$endif}
{$endif}
end;
/// a test case for multi-threading abilities of the framework
// - will test all direct or remote access protocols with a growing number
// of concurrent clients (1,2,5,10,30,50 concurent threads), to ensure
// stability, scalibility and safety of the framework
TTestMultiThreadProcess = class(TSynTestCase)
protected
fModel: TSQLModel;
fDatabase: TSQLRestServerDB;
fTestClass: TSQLRestClass;
fThreads: TObjectList;
fRunningThreadCount: integer;
fHttpServer: TSQLHttpServer;
fMinThreads: integer;
fMaxThreads: integer;
fOperationCount: integer;
fClientPerThread: integer;
fClientOnlyServerIP: RawByteString;
fTimer: TPrecisionTimer;
procedure DatabaseClose;
procedure Test(aClass: TSQLRestClass; aHttp: TSQLHttpServerOptions=HTTP_DEFAULT_MODE;
aWriteMode: TSQLRestServerAcquireMode=amLocked);
function CreateClient: TSQLRest;
public
/// create the test case instance
constructor Create(Owner: TSynTests; const Ident: string = ''); override;
/// release used instances (e.g. server) and memory
procedure CleanUp; override;
/// if not '', forces the test not to initiate any server and connnect to
// the specified server IP address
property ClientOnlyServerIP: RawByteString read fClientOnlyServerIP write fClientOnlyServerIP;
/// the minimum number of threads used for this test
// - is 1 by default
property MinThreads: integer read fMinThreads write fMinThreads;
/// the maximum number of threads used for this test
// - is 50 by default
property MaxThreads: integer read fMaxThreads write fMaxThreads;
/// how many Add() + Retrieve() operations are performed during each test
// - is 200 by default, i.e. 200 Add() plus 200 Retrieve() globally
property OperationCount: integer read fOperationCount write fOperationCount;
/// how many TSQLRest instance is initialized per thread
// - is 1 by default
property ClientPerThread: Integer read fClientPerThread write fClientPerThread;
published
/// initialize fDatabase and create MaxThreads threads for clients
procedure CreateThreadPool;
/// direct test of its RESTful methods
procedure _TSQLRestServerDB;
/// test via TSQLRestClientDB instances
procedure _TSQLRestClientDB;
{$ifdef MSWINDOWS}
/// test via TSQLRestClientURINamedPipe instances
procedure _TSQLRestClientURINamedPipe;
/// test via TSQLRestClientURIMessage instances
procedure _TSQLRestClientURIMessage;
{$endif}
{$ifndef ONLYUSEHTTPSOCKET}
/// test via TSQLHttpClientWinHTTP instances over http.sys (HTTP API) server
procedure WindowsAPI;
{$endif}
/// test via TSQLHttpClientWinSock instances over OS's socket API server
// - this test won't work within the Delphi IDE debugger
procedure SocketAPI;
//// test via TSQLHttpClientWebsockets instances
procedure Websockets;
{$ifdef USELIBCURL}
/// test via TSQLHttpClientCurl using libcurl library
procedure _libcurl;
{$endif}
/// test via TSQLRestClientDB instances with AcquireWriteMode=amLocked
procedure Locked;
/// test via TSQLRestClientDB instances with AcquireWriteMode=amUnlocked
procedure Unlocked;
{$ifndef LVCL}
/// test via TSQLRestClientDB instances with AcquireWriteMode=amMainThread
procedure MainThread;
{$endif}
/// test via TSQLRestClientDB instances with AcquireWriteMode=amBackgroundThread
procedure BackgroundThread;
end;
/// SOA callback definition as expected by TTestBidirectionalRemoteConnection
IBidirCallback = interface(IInvokable)
['{5C5818CC-FFBA-445C-82C1-39F45B84520C}']
procedure AsynchEvent(a: integer);
function Value: Integer;
end;
/// SOA service definition as expected by TTestBidirectionalRemoteConnection
IBidirService = interface(IInvokable)
['{0984A2DA-FD1F-49D6-ACFE-4D45CF08CA1B}']
function TestRest(a,b: integer; out c: RawUTF8): variant;
function TestRestCustom(a: integer): TServiceCustomAnswer;
function TestCallback(d: Integer; const callback: IBidirCallback): boolean;
procedure LaunchAsynchCallback(a: integer);
procedure RemoveCallback;
end;
TBidirServer = class(TInterfacedObject,IBidirService)
protected
fCallback: IBidirCallback;
function TestRest(a,b: integer; out c: RawUTF8): variant;
function TestRestCustom(a: integer): TServiceCustomAnswer;
function TestCallback(d: Integer; const callback: IBidirCallback): boolean;
procedure LaunchAsynchCallback(a: integer);
procedure RemoveCallback;
public
function LaunchSynchCallback: integer;
end;
/// a test case for all bidirectional remote access, e.g. WebSockets
TTestBidirectionalRemoteConnection = class(TSynTestCase)
protected
fHttpServer: TSQLHttpServer;
fServer: TSQLRestServerFullMemory;
fBidirServer: TBidirServer;
procedure CleanUp; override;
procedure WebsocketsLowLevel(protocol: TWebSocketProtocol; opcode: TWebSocketFrameOpCode);
procedure TestRest(Rest: TSQLRest);
procedure TestCallback(Rest: TSQLRest);
procedure SOACallbackViaWebsockets(Ajax: boolean);
published
/// low-level test of our 'synopsejson' WebSockets JSON protocol
procedure WebsocketsJSONProtocol;
/// low-level test of our 'synopsebinary' WebSockets binary protocol
procedure WebsocketsBinaryProtocol;
/// launch the WebSockets-ready HTTP server
procedure RunHttpServer;
/// test the callback mechanism via interface-based services on server side
procedure SOACallbackOnServerSide;
/// test callbacks via interface-based services over JSON WebSockets
procedure SOACallbackViaJSONWebsockets;
/// test callbacks via interface-based services over binary WebSockets
procedure SOACallbackViaBinaryWebsockets;
/// test Master/Slave replication using TRecordVersion field over WebSockets
procedure _TRecordVersion;
end;
type
// This is our simple Test data class. Will be mapped to TSQLRecordDDDTest.
TDDDTest = class(TSynPersistent)
private
fDescription: RawUTF8;
published
property Description: RawUTF8 read fDescription write fDescription;
end;
TDDDTestObjArray = array of TDDDTest;
// The corresponding TSQLRecord for TDDDTest.
TSQLRecordDDDTest = class(TSQLRecord)
private
fDescription: RawUTF8;
published
property Description: RawUTF8 read fDescription write fDescription;
end;
// CQRS Query Interface fo TTest
IDDDThreadsQuery = interface(ICQRSService)
['{DD402806-39C2-4921-98AA-A575DD1117D6}']
function SelectByDescription(const aDescription: RawUTF8): TCQRSResult;
function SelectAll: TCQRSResult;
function Get(out aAggregate: TDDDTest): TCQRSResult;
function GetAll(out aAggregates: TDDDTestObjArray): TCQRSResult;
function GetNext(out aAggregate: TDDDTest): TCQRSResult;
function GetCount: integer;
end;
// CQRS Command Interface for TTest
IDDDThreadsCommand = interface(IDDDThreadsQuery)
['{F0E4C64C-B43A-491B-85E9-FD136843BFCB}']
function Add(const aAggregate: TDDDTest): TCQRSResult;
function Update(const aUpdatedAggregate: TDDDTest): TCQRSResult;
function Delete: TCQRSResult;
function DeleteAll: TCQRSResult;
function Commit: TCQRSResult;
function Rollback: TCQRSResult;
end;
/// a test case for all shared DDD types and services
TTestDDDSharedUnits = class(TSynTestCase)
protected
published
/// test the User modelization types, including e.g. Address
procedure UserModel;
/// test the Authentication modelization types, and implementation
procedure AuthenticationModel;
/// test the Email validation process
procedure EmailValidationProcess;
/// test the CQRS Repository for TUser persistence
procedure UserCQRSRepository;
end;
/// a test case for aggressive multi-threaded DDD ORM test
TTestDDDMultiThread = class(TSynTestCase)
private
// Rest server
fRestServer: TSQLRestServerDB;
// Http server
fHttpServer: TSQLHttpServer;
/// Will create as many Clients as specified by aClient.
// - Each client will perform as many Requests as specified by aRequests.
// - This function will wait for all Clients until finished.
function ClientTest(const aClients, aRequests: integer): boolean;
protected
/// Cleaning up the test
procedure CleanUp; override;
published
/// Delete any old Test database on start
procedure DeleteOldDatabase;
/// Start the whole DDD Server (http and rest)
procedure StartServer;
/// Test straight-forward access using 1 thread and 1 client
procedure SingleClientTest;
/// Test concurrent access with multiple clients
procedure MultiThreadedClientsTest;
end;
/// a test class, used by TTestServiceOrientedArchitecture
// - to test TPersistent objects used as parameters for remote service calls
TComplexNumber = class(TPersistent)
private
fReal: Double;
fImaginary: Double;
public
/// create an instance to store a complex number
constructor Create(aReal, aImaginary: double); reintroduce;
published
/// the real part of this complex number
property Real: Double read fReal write fReal;
/// the imaginary part of this complex number
property Imaginary: Double read fImaginary write fImaginary;
end;
/// a record used by IComplexCalculator.EchoRecord
TConsultaNav = packed record
MaxRows, Row0, RowCount: int64;
IsSQLUpdateBack, EOF: boolean;
end;
/// a record used by IComplexCalculator.GetCustomer
TCustomerData = packed record
Id: Integer;
AccountNum: RawUTF8;
Name: RawUTF8;
Address: RawUTF8;
end;
/// a test interface, used by TTestServiceOrientedArchitecture
// - to test basic and high-level remote service calls
ICalculator = interface(IInvokable)
['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
/// add two signed 32 bit integers
function Add(n1,n2: integer): integer;
/// multiply two signed 64 bit integers
function Multiply(n1,n2: Int64): Int64;
/// substract two floating-point values
function Subtract(n1,n2: double): double;
/// convert a currency value into text
procedure ToText(Value: Currency; var Result: RawUTF8);
/// convert a floating-point value into text
function ToTextFunc(Value: double): string;
/// swap two by-reference floating-point values
// - would validate pointer use instead of XMM1/XMM2 registers under Win64
procedure Swap(var n1,n2: double);
// test unaligned stack access
function StackIntMultiply(n1,n2,n3,n4,n5,n6,n7,n8,n9,n10: integer): Int64;
// test float stack access
function StackFloatMultiply(n1,n2,n3,n4,n5,n6,n7,n8,n9,n10: double): Int64;
/// do some work with strings, sets and enumerates parameters,
// testing also var (in/out) parameters and set as a function result
function SpecialCall(Txt: RawUTF8; var Int: integer; var Card: cardinal; field: TSynTableFieldTypes;
fields: TSynTableFieldTypes; var options: TSynTableFieldOptions): TSynTableFieldTypes;
/// test integer, strings and wide strings dynamic arrays, together with records
function ComplexCall(const Ints: TIntegerDynArray; const Strs1: TRawUTF8DynArray;
var Str2: TWideStringDynArray; const Rec1: TVirtualTableModuleProperties;
var Rec2: TSQLRestCacheEntryValue; Float1: double; var Float2: double): TSQLRestCacheEntryValue;
/// validates ArgsInputIsOctetStream raw binary upload
function DirectCall(const Data: TSQLRawBlob): integer;
/// validates huge RawJSON/RawUTF8
function RepeatJsonArray(const item: RawUTF8; count: integer): RawJSON;
function RepeatTextArray(const item: RawUTF8; count: integer): RawUTF8;
end;
/// a test interface, used by TTestServiceOrientedArchitecture
// - to test remote service calls with objects as parameters (its published
// properties will be serialized as standard JSON objects)
// - since it inherits from ICalculator interface, it will also test
// the proper interface inheritance handling (i.e. it will test that
// ICalculator methods are also available)
IComplexCalculator = interface(ICalculator)
['{8D0F3839-056B-4488-A616-986CF8D4DEB7}']
/// purpose of this method is to substract two complex numbers
// - using class instances as parameters
procedure Substract(n1,n2: TComplexNumber; out Result: TComplexNumber);
/// purpose of this method is to check for boolean handling
function IsNull(n: TComplexNumber): boolean;
/// this will test the BLOB kind of remote answer
function TestBlob(n: TComplexNumber): TServiceCustomAnswer;
{$ifndef NOVARIANTS}
/// test variant kind of parameters
function TestVariants(const Text: RawUTF8; V1: Variant; var V2: variant): variant;
{$endif}
{$ifndef LVCL}
/// test in/out collections
procedure Collections(Item: TCollTest; var List: TCollTestsI; out Copy: TCollTestsI);
{$endif}
/// returns the thread ID running the method on server side
function GetCurrentThreadID: TThreadID;
/// validate record transmission
function GetCustomer(CustomerId: Integer; out CustomerData: TCustomerData): Boolean;
//// validate TSQLRecord transmission
procedure FillPeople(var People: TSQLRecordPeople);
{$ifdef UNICODE}
/// validate simple record transmission
// - older Delphi versions (e.g. 6-7) do not allow records without
// nested reference-counted types
function EchoRecord(const Nav: TConsultaNav): TConsultaNav;
{$endif}
end;
/// a test interface, used by TTestServiceOrientedArchitecture
// - to test sicClientDriven implementation pattern: data will remain on
// the server until the IComplexNumber instance is out of scope
IComplexNumber = interface(IInvokable)
['{29D753B2-E7EF-41B3-B7C3-827FEB082DC1}']
procedure Assign(aReal, aImaginary: double);
function GetImaginary: double;
function GetReal: double;
procedure SetImaginary(const Value: double);
procedure SetReal(const Value: double);
procedure Add(aReal, aImaginary: double);
property Real: double read GetReal write SetReal;
property Imaginary: double read GetImaginary write SetImaginary;
end;
/// a test interface, used by TTestServiceOrientedArchitecture
// - to test sicPerUser implementation pattern
ITestUser = interface(IInvokable)
['{EABB42BF-FD08-444A-BF9C-6B73FA4C4788}']
function GetContextSessionID: integer;
function GetContextSessionUser: integer;
function GetContextSessionGroup: integer;
end;
/// a test interface, used by TTestServiceOrientedArchitecture
// - to test sicPerGroup implementation pattern
ITestGroup = interface(ITestUser)
['{DCBA5A38-62CC-4A52-8639-E709B31DDCE1}']
end;
/// a test interface, used by TTestServiceOrientedArchitecture
// - to test sicPerSession implementation pattern
ITestSession = interface(ITestUser)
['{5237A687-C0B2-46BA-9F39-BEEA7C3AA6A9}']
end;
/// a test interface, used by TTestServiceOrientedArchitecture
// - to test threading implementation pattern
ITestPerThread = interface(IInvokable)
['{202B6C9F-FCCB-488D-A425-5472554FD9B1}']
function GetContextServiceInstanceID: PtrUInt;
function GetThreadIDAtCreation: PtrUInt;
function GetCurrentThreadID: PtrUInt;
function GetCurrentRunningThreadID: PtrUInt;
end;
/// a test value object, used by IUserRepository/ISmsSender interfaces
// - to test stubing/mocking implementation pattern
TUser = record
Name: RawUTF8;
Password: RawUTF8;
MobilePhoneNumber: RawUTF8;
ID: Integer;
end;
/// a test interface, used by TTestServiceOrientedArchitecture
// - to test stubing/mocking implementation pattern
IUserRepository = interface(IInvokable)
['{B21E5B21-28F4-4874-8446-BD0B06DAA07F}']
function GetUserByName(const Name: RawUTF8): TUser;
procedure Save(const User: TUser);
end;
/// a test interface, used by TTestServiceOrientedArchitecture
// - to test stubing/mocking implementation pattern
ISmsSender = interface(IInvokable)
['{8F87CB56-5E2F-437E-B2E6-B3020835DC61}']
function Send(const Text, Number: RawUTF8): boolean;
end;
const
IID_ICalculator: TGUID = '{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}';
type
TTestServiceInstances = record
I: ICalculator;
CC: IComplexCalculator;
CN: IComplexNumber;
CU: ITestUser;
CG: ITestGroup;
CS: ITestSession;
CT: ITestPerThread;
ExpectedSessionID: integer;
ExpectedUserID: integer;
ExpectedGroupID: integer;
end;
/// a test case which will test the interface-based SOA implementation of
// the mORMot framework
TTestServiceOrientedArchitecture = class(TSynTestCase)
protected
fModel: TSQLModel;
fClient: TSQLRestClientDB;
procedure Test(const Inst: TTestServiceInstances; Iterations: Cardinal=700);
procedure ClientTest(aRouting: TSQLRestServerURIContextClass;
aAsJSONObject: boolean; {$ifndef LVCL}aRunInOtherThread: boolean=false;{$endif}
aOptions: TServiceMethodOptions=[]);
procedure ClientAlgo(algo: TSQLRestServerAuthenticationSignedURIAlgo);
class function CustomReader(P: PUTF8Char; var aValue; out aValid: Boolean): PUTF8Char;
class procedure CustomWriter(const aWriter: TTextWriter; const aValue);
procedure SetOptions(aAsJSONObject: boolean;
aOptions: TServiceMethodOptions);
procedure IntSubtractJSON(Ctxt: TOnInterfaceStubExecuteParamsJSON);
{$ifndef NOVARIANTS}
procedure IntSubtractVariant(Ctxt: TOnInterfaceStubExecuteParamsVariant);
procedure IntSubtractVariantVoid(Ctxt: TOnInterfaceStubExecuteParamsVariant);
{$endif}
/// release used instances (e.g. http server) and memory
procedure CleanUp; override;
public
published
/// test the SetWeak/SetWeakZero weak interface functions
procedure WeakInterfaces;
/// initialize the SOA implementation
procedure ServiceInitialization;
/// test direct call to the class instance
procedure DirectCall;
/// test the server-side implementation
procedure ServerSide;
/// test the client-side implementation in RESTful mode
procedure ClientSideREST;
/// test the client-side in RESTful mode with values transmitted as JSON objects
procedure ClientSideRESTAsJSONObject;
/// test the client-side in RESTful mode with full session statistics
procedure ClientSideRESTSessionsStats;
/// test the client-side implementation of optExecLockedPerInterface
procedure ClientSideRESTLocked;
{$ifndef LVCL}
/// test the client-side implementation of opt*InMainThread option
procedure ClientSideRESTMainThread;
/// test the client-side implementation of opt*InPerInterfaceThread option
procedure ClientSideRESTBackgroundThread;
{$endif}
/// test the client-side implementation with crc32c URI signature
procedure ClientSideRESTSignWithCrc32c;
/// test the client-side implementation with xxHash32 URI signature
procedure ClientSideRESTSignWithXxhash;
/// test the client-side implementation with MD5 URI signature
procedure ClientSideRESTSignWithMd5;
/// test the client-side implementation with SHA256 URI signature
procedure ClientSideRESTSignWithSha256;
/// test the client-side implementation with SHA512 URI signature
procedure ClientSideRESTSignWithSha512;
/// test the client-side implementation using TSQLRestServerAuthenticationNone
procedure ClientSideRESTWeakAuthentication;
/// test the client-side implementation using TSQLRestServerAuthenticationHttpBasic
procedure ClientSideRESTBasicAuthentication;
/// test the custom record JSON serialization
procedure ClientSideRESTCustomRecordLayout;
/// test the client-side in RESTful mode with all calls logged in a table
procedure ClientSideRESTServiceLogToDB;
/// test the client-side implementation in JSON-RPC mode
procedure ClientSideJSONRPC;
/// test REStful mode using HTTP client/server communication
procedure TestOverHTTP;
/// test the security features
procedure Security;
/// test interface stubbing / mocking
procedure MocksAndStubs;
end;
{$endif DELPHI5OROLDER}
implementation
uses
{$ifndef DELPHI5OROLDER}
TestSQL3FPCInterfaces,
{$endif}
{$ifndef LVCL}
SyncObjs,
{$endif}
{$ifdef MSWINDOWS}
PasZip,
{$ifndef FPC}
{$ifdef ISDELPHIXE2}
VCL.Graphics,
{$else}
Graphics,
{$endif}
{$endif}
{$endif}
SynCrypto,
SynZip,
SynLZO,
SynLZ,
SynLizard;
{ TTestLowLevelCommon }
procedure TTestLowLevelCommon._CamelCase;
var v: RawUTF8;
begin
v := UnCamelCase('On'); Check(v='On');
v := UnCamelCase('ON'); Check(v='ON');
v := UnCamelCase('OnLine'); Check(v='On line');
v := UnCamelCase('OnLINE'); Check(v='On LINE');
v := UnCamelCase('OnMyLINE'); Check(v='On my LINE');
v := UnCamelCase('On_MyLINE'); Check(v='On - My LINE');
v := UnCamelCase('On__MyLINE'); Check(v='On: My LINE');
v := UnCamelCase('Email1'); Check(v='Email 1');
v := UnCamelCase('Email12'); Check(v='Email 12');
v := UnCamelCase('KLMFlightNumber'); Check(v='KLM flight number');
v := UnCamelCase('GoodBBCProgram'); Check(v='Good BBC program');
end;
procedure TTestLowLevelCommon.Bits;
var Bits: array[byte] of byte;
Bits64: Int64 absolute Bits;
Si,i: integer;
c: cardinal;
begin
FillcharFast(Bits,sizeof(Bits),0);
for i := 0 to high(Bits)*8+7 do
Check(not GetBit(Bits,i));
RandSeed := 10; // will reproduce the same Random() values
for i := 1 to 100 do begin
Si := Random(high(Bits));
SetBit(Bits,Si);
Check(GetBit(Bits,Si));
end;
RandSeed := 10;
for i := 1 to 100 do
Check(GetBit(Bits,Random(high(Bits))));
RandSeed := 10;
for i := 1 to 100 do begin
Si := Random(high(Bits));
UnSetBit(Bits,Si);
Check(not GetBit(Bits,Si));
end;
for i := 0 to high(Bits)*8+7 do
Check(not GetBit(Bits,i));
for i := 0 to 63 do
Check(not GetBit64(Bits64,i));
RandSeed := 10;
for i := 1 to 30 do begin
Si := Random(63);
SetBit64(Bits64,Si);
Check(GetBit64(Bits64,Si));
end;
RandSeed := 10;
for i := 1 to 30 do
Check(GetBit64(Bits64,Random(63)));
RandSeed := 10;
for i := 1 to 30 do begin
Si := Random(63);
UnSetBit64(Bits64,Si);
Check(not GetBit64(Bits64,Si));
end;
for i := 0 to 63 do
Check(not GetBit64(Bits64,i));
c := 1;
for i := 1 to 32 do begin
Check(GetAllBits($ffffffff,i));
Check(not GetAllBits(0,i));
Check(GetAllBits(c,i));
Check(not GetAllBits(c and -2,i));
Check(GetAllBits(ALLBITS_CARDINAL[i],i));
c := c or (1 shl i);
end;
Randomize; // we fixed the RandSeed value above -> get true random now
end;
procedure TTestLowLevelCommon.Curr64;
var tmp: string[63];
i, err: Integer;
V1: currency;
V2: TSynExtended;
i64: Int64;
v: RawUTF8;
begin
Check(TruncTo2Digits(1)=1);
Check(TruncTo2Digits(1.05)=1.05);
Check(TruncTo2Digits(1.051)=1.05);
Check(TruncTo2Digits(1.0599)=1.05);
Check(TruncTo2Digits(-1)=-1);
Check(TruncTo2Digits(-1.05)=-1.05);
Check(TruncTo2Digits(-1.051)=-1.05);
Check(TruncTo2Digits(-1.0599)=-1.05);
Check(SimpleRoundTo2Digits(1)=1);
Check(SimpleRoundTo2Digits(1.05)=1.05);
Check(SimpleRoundTo2Digits(1.051)=1.05);
Check(SimpleRoundTo2Digits(1.0549)=1.05);
Check(SimpleRoundTo2Digits(1.0550)=1.05);
Check(SimpleRoundTo2Digits(1.0551)=1.06);
Check(SimpleRoundTo2Digits(1.0599)=1.06);
Check(SimpleRoundTo2Digits(-1)=-1);
Check(SimpleRoundTo2Digits(-1.05)=-1.05);
Check(SimpleRoundTo2Digits(-1.051)=-1.05);
Check(SimpleRoundTo2Digits(-1.0549)=-1.05);
Check(SimpleRoundTo2Digits(-1.0550)=-1.05);
Check(SimpleRoundTo2Digits(-1.0551)=-1.06);
Check(SimpleRoundTo2Digits(-1.0599)=-1.06);
Check(StrToCurr64('.5')=5000);
Check(StrToCurr64('.05')=500);
Check(StrToCurr64('.005')=50);
Check(StrToCurr64('.0005')=5);
Check(StrToCurr64('.00005')=0);
Check(StrToCurr64('0.5')=5000);
Check(StrToCurr64('0.05')=500);
Check(StrToCurr64('0.005')=50);
Check(StrToCurr64('0.0005')=5);
Check(StrToCurr64('0.00005')=0);
Check(StrToCurr64('1.5')=15000);
Check(StrToCurr64('1.05')=10500);
Check(StrToCurr64('1.005')=10050);
Check(StrToCurr64('1.0005')=10005);
Check(StrToCurr64('1.00005')=10000);
Check(StrToCurr64(pointer(Curr64ToStr(1)))=1);
Check(StrToCurr64(pointer(Curr64ToStr(12)))=12);
Check(StrToCurr64(pointer(Curr64ToStr(123)))=123);
Check(StrToCurr64(pointer(Curr64ToStr(1234)))=1234);
Check(StrToCurr64(pointer(Curr64ToStr(12345)))=12345);
Check(StrToCurr64(pointer(Curr64ToStr(123456)))=123456);
Check(StrToCurr64(pointer(Curr64ToStr(12340000)))=12340000);
Check(StrToCurr64(pointer(Curr64ToStr(12345000)))=12345000);
Check(StrToCurr64(pointer(Curr64ToStr(12345600)))=12345600);
Check(StrToCurr64(pointer(Curr64ToStr(12345670)))=12345670);
Check(StrToCurr64(pointer(Curr64ToStr(12345678)))=12345678);
tmp[0] := AnsiChar(Curr64ToPChar(1,@tmp[1])); Check(tmp='0.0001');
tmp[0] := AnsiChar(Curr64ToPChar(12,@tmp[1])); Check(tmp='0.0012');
tmp[0] := AnsiChar(Curr64ToPChar(123,@tmp[1])); Check(tmp='0.0123');
tmp[0] := AnsiChar(Curr64ToPChar(1234,@tmp[1])); Check(tmp='0.1234');
for i := 0 to 5000 do begin
if i<500 then
V1 := i*3 else
V1 := Random*(Int64(MaxInt)*10);
if Random(10)<4 then
V1 := -V1;
v := Curr64ToStr(PInt64(@V1)^);
tmp[0] := AnsiChar(Curr64ToPChar(PInt64(@V1)^,@tmp[1]));
Check(RawUTF8(tmp)=v);
V2 := GetExtended(pointer(v),err);
Check(err=0);
CheckSame(V1,V2,1E-4);
i64 := StrToCurr64(pointer(v));
Check(PInt64(@V1)^=i64);
end;
end;
procedure TTestLowLevelCommon.FastStringCompare;
begin
Check(CompareText('','')=0);
Check(CompareText('abcd','')>0);
Check(CompareText('','abcd')<0);
Check(StrCompFast(nil,nil)=0);
Check(StrCompFast(PAnsiChar('abcD'),nil)=1);
Check(StrCompFast(nil,PAnsiChar('ABcd'))=-1);
Check(StrCompFast(PAnsiChar('ABCD'),PAnsiChar('ABCD'))=0);
Check(StrCompFast(PAnsiChar('ABCD'),PAnsiChar('ABCE'))=-1);
Check(StrCompFast(PAnsiChar('ABCD'),PAnsiChar('ABCC'))=1);
Check(StrIComp(nil,nil)=0);
Check(StrIComp(PAnsiChar('abcD'),nil)=1);
Check(StrIComp(nil,PAnsiChar('ABcd'))=-1);
Check(StrIComp(PAnsiChar('abcD'),PAnsiChar('ABcd'))=0);
Check(StrIComp(PAnsiChar('abcD'),PAnsiChar('ABcF'))=
StrComp(PAnsiChar('ABCD'),PAnsiChar('ABCF')));
Check(StrComp(PAnsiChar('ABCD'),PAnsiChar('ABCE'))=-1);
Check(StrComp(PAnsiChar('ABCD'),PAnsiChar('ABCC'))=1);
Check(StrComp(nil,nil)=0);
Check(StrComp(PAnsiChar('ABCD'),PAnsiChar('ABCD'))=0);
Check(StrComp(PAnsiChar('ABCD'),PAnsiChar('ABCE'))=-1);
Check(StrComp(PAnsiChar('ABCD'),PAnsiChar('ABCC'))=1);
Check(AnsiIComp('abcD','ABcd')=0);
Check(AnsiIComp('abcD','ABcF')=StrComp(PAnsiChar('ABCD'),PAnsiChar('ABCF')));
Check(StrIComp(PAnsiChar('abcD'),PAnsiChar('ABcd'))=AnsiIComp('abcD','ABcd'));
Check(StrIComp(PAnsiChar('abcD'),PAnsiChar('ABcF'))=AnsiIComp('ABCD','ABCF'));
Check(strcspn(PAnsiChar('ab'),PAnsiChar('a'#0))=0);
Check(strcspn(PAnsiChar('ab'),PAnsiChar('b'#0))=1);
Check(strcspn(PAnsiChar('1234ab'),PAnsiChar('a'#0))=4);
Check(strcspn(PAnsiChar('12345ab'),PAnsiChar('a'#0))=5);
Check(strcspn(PAnsiChar('123456ab'),PAnsiChar('a'#0))=6);
Check(strcspn(PAnsiChar('1234567ab'),PAnsiChar('a'#0))=7);
Check(strcspn(PAnsiChar('12345678ab'),PAnsiChar('a'#0))=8);
Check(strcspn(PAnsiChar('1234ab'),PAnsiChar('c'#0))=6);
Check(strcspnpas(PAnsiChar('ab'),PAnsiChar('a'#0))=0);
Check(strcspnpas(PAnsiChar('ab'),PAnsiChar('b'#0))=1);
Check(strcspnpas(PAnsiChar('1234ab'),PAnsiChar('a'#0))=4);
Check(strcspnpas(PAnsiChar('12345ab'),PAnsiChar('a'#0))=5);
Check(strcspnpas(PAnsiChar('123456ab'),PAnsiChar('a'#0))=6);
Check(strcspnpas(PAnsiChar('1234567ab'),PAnsiChar('a'#0))=7);
Check(strcspnpas(PAnsiChar('12345678ab'),PAnsiChar('a'#0))=8);
Check(strcspnpas(PAnsiChar('1234ab'),PAnsiChar('c'#0))=6);
Check(strcspnpas(PAnsiChar('12345678901234567ab'),PAnsiChar('cccccccccccccccccccd'))=19);
Assert(strspn(PAnsiChar('abcdef'),PAnsiChar('debca'))=5);
Assert(strspn(PAnsiChar('baabbaabcd'),PAnsiChar('ab'))=8);
Assert(strspnpas(PAnsiChar('abcdef'),PAnsiChar('g'#0))=0);
Assert(strspnpas(PAnsiChar('abcdef'),PAnsiChar('a'#0))=1);
Assert(strspnpas(PAnsiChar('bbcdef'),PAnsiChar('b'#0))=2);
Assert(strspnpas(PAnsiChar('bbcdef'),PAnsiChar('bf'))=2);
Assert(strspnpas(PAnsiChar('bcbdef'),PAnsiChar('cb'))=3);
Assert(strspnpas(PAnsiChar('baabcd'),PAnsiChar('ab'))=4);
Assert(strspnpas(PAnsiChar('abcdef'),PAnsiChar('debca'))=5);
Assert(strspnpas(PAnsiChar('baabbaabcd'),PAnsiChar('ab'))=8);
Assert(strspnpas(PAnsiChar('baabbaabbaabcd'),PAnsiChar('ab'))=12);
Assert(strspnpas(PAnsiChar('baabbaabbaabbabcd'),PAnsiChar('ab'))=15);
Assert(strspnpas(PAnsiChar('baabbaabbaabbaabcd'),PAnsiChar('ab'))=16);
Assert(strspnpas(PAnsiChar('baabbaabbaababaabcd'),PAnsiChar('ab'))=17);
{$ifdef CPUINTEL}
if cfSSE42 in CpuFeatures then begin
Check(strcspnsse42(PAnsiChar('ab'),PAnsiChar('a'#0))=0);
Check(strcspnsse42(PAnsiChar('ab'),PAnsiChar('b'#0))=1);
Check(strcspnsse42(PAnsiChar('1234ab'),PAnsiChar('a'#0))=4);
Check(strcspnsse42(PAnsiChar('12345ab'),PAnsiChar('a'#0))=5);
Check(strcspnsse42(PAnsiChar('123456ab'),PAnsiChar('a'#0))=6);
Check(strcspnsse42(PAnsiChar('1234567ab'),PAnsiChar('a'#0))=7);
Check(strcspnsse42(PAnsiChar('12345678ab'),PAnsiChar('a'#0))=8);
Check(strcspnsse42(PAnsiChar('123456789ab'),PAnsiChar('a'#0))=9);
Check(strcspnsse42(PAnsiChar('1234ab'),PAnsiChar('c'#0))=6);
Check(strcspnsse42(PAnsiChar('123456789012345ab'),PAnsiChar('a'#0))=15);
Check(strcspnsse42(PAnsiChar('1234567890123456ab'),PAnsiChar('a'#0))=16);
Check(strcspnsse42(PAnsiChar('12345678901234567ab'),PAnsiChar('a'#0))=17);
Check(strcspnsse42(PAnsiChar('12345678901234567ab'),PAnsiChar('cccccccccccccca'))=17);
Check(strcspnsse42(PAnsiChar('12345678901234567ab'),PAnsiChar('ccccccccccccccca'))=17);
Check(strcspnsse42(PAnsiChar('12345678901234567ab'),PAnsiChar('cccccccccccccccca'))=17);
Check(strcspnsse42(PAnsiChar('12345678901234567ab'),PAnsiChar('ccccccccccccccccca'))=17);
Check(strcspnsse42(PAnsiChar('12345678901234567ab'),PAnsiChar('ccccccccccccccccccca'))=17);
Check(strcspnsse42(PAnsiChar('12345678901234567ab'),PAnsiChar('cccccccccccccccccccd'))=19);
Check(strspnsse42(PAnsiChar('abcdef'),PAnsiChar('g'#0))=0);
Check(strspnsse42(PAnsiChar('abcdef'),PAnsiChar('a'#0))=1);
Check(strspnsse42(PAnsiChar('bbcdef'),PAnsiChar('b'#0))=2);
Check(strspnsse42(PAnsiChar('bbcdef'),PAnsiChar('bf'))=2);
Check(strspnsse42(PAnsiChar('bcbdef'),PAnsiChar('cb'))=3);
Check(strspnsse42(PAnsiChar('baabcd'),PAnsiChar('ab'))=4);
Check(strspnsse42(PAnsiChar('abcdef'),PAnsiChar('debca'))=5);
Check(strspnsse42(PAnsiChar('baabbaabcd'),PAnsiChar('ab'))=8);
Check(strspnsse42(PAnsiChar('baabbaabbaabcd'),PAnsiChar('ab'))=12);
Check(strspnsse42(PAnsiChar('baabbaabbaabbabcd'),PAnsiChar('ab'))=15);
Check(strspnsse42(PAnsiChar('baabbaabbaabbaabcd'),PAnsiChar('ab'))=16);
Check(strspnsse42(PAnsiChar('baabbaabbaababaabcd'),PAnsiChar('ab'))=17);
end;
{$endif CPUINTEL}
end;
procedure TTestLowLevelCommon.IniFiles;
var Content,S,N,V: RawUTF8;
Si,Ni,Vi,i,j: integer;
P: PUTF8Char;
begin
Content := '';
Randomize;
//RandSeed := 10;
for i := 1 to 1000 do begin
Si := Random(20);
Ni := Random(50);
Vi := Si*Ni+Ni;
if Si=0 then
S := '' else
S := 'Section'+{$ifndef ENHANCEDRTL}Int32ToUtf8{$else}IntToStr{$endif}(Si);
N := {$ifndef ENHANCEDRTL}Int32ToUtf8{$else}IntToStr{$endif}(Ni);
V := {$ifndef ENHANCEDRTL}Int32ToUtf8{$else}IntToStr{$endif}(Vi);
UpdateIniEntry(Content,S,N,V);
for j := 1 to 5 do
Check(FindIniEntry(Content,S,N)=V,'FindIniEntry');
Check(FindIniEntry(Content,S,'no')='');
Check(FindIniEntry(Content,'no',N)='');
end;
Check(FileFromString(Content,'test.ini'),'test.ini');
Check(FileSynLZ('test.ini','test.ini.synlz',$ABA51051),'synLZ');
if CheckFailed(FileUnSynLZ('test.ini.synlz','test2.ini',$ABA51051),'unSynLZ') then
Exit;
S := StringFromFile('test2.ini');
Check(S=Content,'test2.ini');
Content := 'abc'#13#10'def'#10'ghijkl'#13'1234567890';
P := pointer(Content);
Check(GetNextLine(P,P)='abc');
Check(GetNextLine(P,P)='def');
Check(GetNextLine(P,P)='ghijkl');
Check(GetNextLine(P,P)='1234567890');
Check(P=nil);
end;
procedure TTestLowLevelCommon.Soundex;
var e: cardinal;
PC: PAnsiChar;
Soundex: TSynSoundEx;
s: WinAnsiString;
begin
Check(SoundExAnsi(PAnsiChar(' 120 '))=0);
if SOUNDEX_BITS=8 then
e := $2050206 else
e := $2526;
Check(SoundExAnsi(PAnsiChar('bonjour'))=e);
Check(SoundExAnsi(PAnsiChar(' 123 bonjour. m'),@PC)=e);
Check((PC<>nil) and (PC^='.'));
s := ' 123 bonjourtreslongmotquidepasse m';
s[15] := #232;
s[28] := #233;
Check(SoundExAnsi(pointer(s),@PC)<>0);
Check((PC<>nil) and (PC^=' '));
Check(SoundExAnsi(PAnsiChar('BOnjour'))=e);
Check(SoundExAnsi(PAnsiChar('Bnjr'))=e);
Check(SoundExAnsi(PAnsiChar('bonchour'))=e);
Check(SoundExAnsi(PAnsiChar('mohammad'))=SoundExAnsi(PAnsiChar('mohhhammeeet')));
if SOUNDEX_BITS=8 then
e := $2050206 else
e := $25262;
Check(SoundExAnsi(PAnsiChar('bonjours'))=e);
Check(SoundExAnsi(PAnsiChar('BOnjours'))=e);
Check(SoundExAnsi(PAnsiChar('Bnjrs'))=e);
Check(SoundExAnsi(PAnsiChar(' 120 '))=0);
if SOUNDEX_BITS=8 then
e := $2050206 else
e := $2526;
Check(SoundExUTF8('bonjour')=e);
Check(SoundExUTF8(' 123 bonjour. m',@PC)=e);
Check((PC<>nil) and (PC^='m'));
Check(SoundExUTF8(Pointer(WinAnsiToUTF8(s)),@PC)<>0);
Check((PC<>nil) and (PC^='m'));
Check(SoundExUTF8('BOnjour')=e);
Check(SoundExUTF8('Bnjr')=e);
Check(SoundExUTF8('bonchour')=e);
Check(SoundExUTF8('mohammad')=SoundExUTF8('mohhhammeeet'));
if SOUNDEX_BITS=8 then
e := $2050206 else
e := $25262;
Check(SoundExUTF8('bonjours')=e);
Check(SoundExUTF8('BOnjours')=e);
Check(SoundExUTF8('Bnjrs')=e);
Check(Soundex.Prepare(PAnsiChar('mohamad'),sndxEnglish));
Check(Soundex.Ansi('moi rechercher mohammed ici'));
Check(Soundex.UTF8('moi rechercher mohammed ici'));
Check(Soundex.Ansi('moi mohammed'));
Check(Soundex.UTF8('moi mohammed'));
Check(not Soundex.Ansi('moi rechercher mouette ici'));
Check(not Soundex.UTF8('moi rechercher mouette ici'));
Check(not Soundex.Ansi('moi rechercher mouette'));
Check(not Soundex.UTF8('moi rechercher mouette'));
end;
procedure TTestLowLevelCommon._TRawUTF8List;
const MAX=20000;
var i: integer;
L: TRawUTF8List;
C: TComponent;
Rec: TSynFilterOrValidate;
s: RawUTF8;
begin
L := TRawUTF8List.Create(true);
try
for i := 0 to MAX do begin
C := TComponent.Create(nil);
C.Tag := i;
Check(L.AddObject(UInt32ToUtf8(i),C)=i);
end;
Check(L.Count=MAX+1);
for i := 0 to MAX do
Check(GetInteger(Pointer(L[i]))=i);
for i := 0 to MAX do
Check(TComponent(L.Objects[i]).Tag=i);
for i := MAX downto 0 do
if i and 1=0 then
L.Delete(i);
Check(L.Count=MAX div 2);
for i := 0 to L.Count-1 do
Check(GetInteger(Pointer(L[i]))=TComponent(L.Objects[i]).Tag);
finally
L.Free;
end;
L := TRawUTF8ListHashed.Create(true);
try
for i := 1 to MAX do begin
Rec := TSynFilterOrValidate.create;
Rec.Parameters := Int32ToUTF8(i);
L.AddObjectIfNotExisting(Rec.Parameters,Rec);
end;
Check(L.IndexOf('')<0);
Check(L.IndexOf('abcd')<0);
for i := 1 to MAX do begin
UInt32ToUTF8(i,s);
Check(L.IndexOf(s)=i-1);
Check(TSynFilterOrValidate(L.Objects[i-1]).Parameters=s);
end;
L.SaveToFile('utf8list.txt');
L.Clear;
Check(L.Count=0);
L.LoadFromFile('utf8list.txt');
Check(L.Count=MAX);
for i := 1 to MAX do begin
UInt32ToUTF8(i,s);
Check(L.IndexOf(s)=i-1);
end;
DeleteFile('utf8list.txt');
finally
L.Free;
end;
end;
type
TCity = record
Name: string;
Country: string;
Latitude: double;
Longitude: double;
end;
TCityDynArray = array of TCity;
TAmount = packed record
firmID: integer;
amount: RawUTF8;
end;
TAmountCollection = array of TAmount;
TAmountI = packed record
firmID: integer;
amount: integer;
end;
TAmountICollection = array of TAmountI;
procedure TTestLowLevelCommon._TDynArrayHashed;
var ACities: TDynArrayHashed;
Cities: TCityDynArray;
CitiesCount: integer;
City: TCity;
added: boolean;
N: string;
i,j: integer;
A: TAmount;
AI: TAmountI;
AmountCollection: TAmountCollection;
AmountICollection: TAmountICollection;
AmountDA,AmountIDA1,AmountIDA2: TDynArrayHashed;
const CITIES_MAX=200000;
begin
// default Init() will hash and compare binary content before string, i.e. firmID
AmountDA.Init(TypeInfo(TAmountCollection), AmountCollection);
Check(AmountDA.KnownType=djInteger);
Check(@AmountDA.HashElement=@HashInteger);
for i := 1 to 100 do begin
A.firmID := i;
A.amount := UInt32ToUTF8(i);
Check(AmountDA.Add(A)=i-1);
end;
AmountDA.ReHash;
for i := 1 to length(AmountCollection) do
Check(AmountDA.FindHashed(i)=i-1);
// default Init() will hash and compare the WHOLE binary content, i.e. 8 bytes
AmountIDA1.Init(TypeInfo(TAmountICollection), AmountICollection);
Check(AmountIDA1.KnownType=djInt64);
Check(@AmountIDA1.HashElement=@HashInt64);
for i := 1 to 100 do begin
AI.firmID := i;
AI.amount := i*2;
Check(AmountIDA1.Add(AI)=i-1);
end;
AmountIDA1.ReHash;
for i := 1 to length(AmountICollection) do begin
AI.firmID := i;
AI.amount := i*2;
Check(AmountIDA1.FindHashed(AI)=i-1);
end;
AmountIDA1.Clear;
// specific hash & compare of the firmID integer first field
AmountIDA2.InitSpecific(TypeInfo(TAmountICollection), AmountICollection, djInteger);
Check(AmountIDA2.KnownType=djInteger);
Check(@AmountIDA2.HashElement=@HashInteger);
for i := 1 to 100 do begin
AI.firmID := i;
AI.amount := i*2;
Check(AmountIDA2.Add(AI)=i-1);
end;
AmountIDA2.ReHash;
for i := 1 to length(AmountICollection) do
Check(AmountIDA2.FindHashed(i)>=0);
// valide generic-like features
// see http://docwiki.embarcadero.com/CodeExamples/en/Generics_Collections_TDictionary_(Delphi)
ACities.Init(TypeInfo(TCityDynArray),Cities,nil,nil,nil,@CitiesCount);
City.Name := 'Iasi';
City.Country := 'Romania';
City.Latitude := 47.16;
City.Longitude := 27.58;
ACities.Add(City);
City.Name := 'London';
City.Country := 'United Kingdom';
City.Latitude := 51.5;
City.Longitude := -0.17;
ACities.Add(City);
City.Name := 'Buenos Aires';
City.Country := 'Argentina';
City.Latitude := 0;
City.Longitude := 0;
ACities.Add(City);
Check(ACities.Count=3);
ACities.ReHash; // will use default hash, and search by Name = 1st field
City.Name := 'Iasi';
Check(ACities.FindHashedAndFill(City)=0);
Check(City.Name='Iasi');
Check(City.Country='Romania');
CheckSame(City.Latitude,47.16);
CheckSame(City.Longitude,27.58);
Check(ACities.FindHashedAndDelete(City)=0);
Check(City.Name='Iasi');
Check(ACities.Scan(City)<0);
Check(ACities.FindHashed(City)<0);
City.Name := 'Buenos Aires';
City.Country := 'Argentina';
City.Latitude := -34.6;
City.Longitude := -58.45;
Check(ACities.FindHashedAndUpdate(City,false)>=0);
City.Latitude := 0;
City.Longitude := 0;
Check(City.Name='Buenos Aires');
Check(ACities.FindHashedAndFill(City)>=0);
CheckSame(City.Latitude,-34.6);
CheckSame(City.Longitude,-58.45);
Check(ACities.FindHashedForAdding(City,added)>=0);
Check(not added);
City.Name := 'Iasi';
City.Country := 'Romania';
City.Latitude := 47.16;
City.Longitude := 27.58;
i := ACities.FindHashedForAdding(City,added);
Check(added);
Check(i>0);
if i>0 then begin
Check(Cities[i].Name=''); // FindHashedForAdding left void content
Cities[i] := City; // should fill Cities[i] content by hand
end;
Check(ACities.Count=3);
Check(City.Name='Iasi');
Check(ACities.FindHashed(City)>=0);
for i := 1 to 2000 do begin
City.Name := IntToString(i);
City.Latitude := i*3.14;
City.Longitude := i*6.13;
Check(ACities.FindHashedAndUpdate(City,true)=i+2,'multiple ReHash');
Check(ACities.FindHashed(City)=i+2);
end;
ACities.Capacity := CITIES_MAX+3; // make it as fast as possible
for i := 2001 to CITIES_MAX do begin
City.Name := IntToString(i);
City.Latitude := i*3.14;
City.Longitude := i*6.13;
Check(ACities.FindHashedAndUpdate(City,true)=i+2,'use Capacity: no ReHash');
Check(ACities.FindHashed(City.Name)=i+2);
end;
for i := 1 to CITIES_MAX do begin
N := IntToString(i);
j := ACities.FindHashed(N);
Check(j=i+2,'hashing with string not City.Name');
Check(Cities[j].Name=N);
CheckSame(Cities[j].Latitude,i*3.14);
CheckSame(Cities[j].Longitude,i*6.13);
end;
end;
type
TRec = packed record A: integer; B: byte; C: double; D: Currency; end;
TRecs = array of TRec;
TProvince = record
Name: RawUTF8;
Comment: RawUTF8;
Year: cardinal;
Cities: TCityDynArray;
end;
TFV = packed record
Major, Minor, Release, Build: integer;
Main, Detailed: string;
BuildDateTime: TDateTime;
BuildYear: integer;
end;
TFVs = array of TFV;
TFV2 = packed record
V1: TFV;
Value: integer;
V2: TFV;
Text: string;
end;
TFV2s = array of TFV2;
TSynValidates = array of TSynValidate;
TDataItem = record
Modified: TDateTime;
Data: string;
end;
TDataItems = array of TDataItem;
function FVSort(const A,B): integer;
begin
result := SysUtils.StrComp(PChar(pointer(TFV(A).Detailed)),PChar(pointer(TFV(B).Detailed)));
end;
procedure TTestLowLevelCommon._TDynArray;
var AI, AI2: TIntegerDynArray;
AU: TRawUTF8DynArray;
AR: TRecs;
AF: TFVs;
AF2: TFV2s;
h: cardinal;
i,j,k,Len, count,AIcount: integer;
U,U2: RawUTF8;
P: PUTF8Char;
PI: PIntegerArray;
AB: TBooleanDynArray;
R: TRec;
F, F1: TFV;
F2: TFV2;
City: TCity;
Province: TProvince;
AV: TSynValidates;
V: TSynValidate;
AIP, AI2P, AUP, ARP, AFP, ACities, AVP, dyn1,dyn2: TDynArray;
dyniter: TDynArrayLoadFrom;
B: boolean;
dp: TDataItem;
dyn1Array,dyn2Array: TDataItems;
Test, Test2: RawByteString;
ST: TCustomMemoryStream;
Index: TIntegerDynArray;
W: TTextWriter;
JSON_BASE64_MAGIC_UTF8: RawUTF8;
const MAGIC: array[0..1] of word = (34,$fff0);
procedure Fill(var F: TFV; i: integer);
begin
F.Major := i;
F.Minor := i+1;
F.Release := i+2;
F.Build := i+3;
F.Main := IntToString(i+1000);
F.Detailed := IntToString(2000-i);
F.BuildDateTime := 36215.12;
F.BuildYear := i+2011;
end;
procedure TestAF2;
var i: integer;
F1,F2: TFV;
begin
for i := 0 to AFP.Count-1 do begin
Check(AF2[i].Value=i);
Check(AF2[i].Text=IntToString(i));
Fill(F1,i*2);
Fill(F2,i*2+1);
Check(RecordEquals(F1,AF2[i].V1,TypeInfo(TFV)));
Check(RecordEquals(F2,AF2[i].V2,TypeInfo(TFV)));
end;
end;
procedure Test64K;
var i, E, n: integer;
D: TDynArray;
IA: TIntegerDynArray;
begin
D.Init(TypeInfo(TIntegerDynArray),IA,@n);
D.Capacity := 16300;
for i := 0 to 16256 do begin
E := i*5;
Check(D.Add(E)=i);
Check(IA[i]=i*5);
end;
Check(D.Count=16257);
Check(D.Capacity=16300);
Check(length(IA)=D.Capacity);
for i := 0 to 16256 do
Check(IA[i]=i*5);
Check(Hash32(D.SaveTo)=$36937D84);
end;
procedure TestCities;
var i: integer;
begin
for i := 0 to ACities.Count-1 do
with Province.Cities[i] do begin
{$ifdef UNICODE}
Check(StrToInt(Name)=i);
{$else}
Check(GetInteger(pointer(Name))=i);
{$endif}
CheckSame(Latitude,i*3.14);
CheckSame(Longitude,i*6.13);
end;
end;
begin
h := TypeInfoToHash(TypeInfo(TAmount));
Check(h=$9032161B,'TypeInfoToHash(TAmount)');
h := TypeInfoToHash(TypeInfo(TAmountCollection));
Check(h=$887ED692,'TypeInfoToHash(TAmountCollection)');
h := TypeInfoToHash(TypeInfo(TAmountICollection));
Check(h=$4051BAC,'TypeInfoToHash(TAmountICollection)');
W := TTextWriter.CreateOwnedStream;
// validate TBooleanDynArray
dyn1.Init(TypeInfo(TBooleanDynArray),AB);
SetLength(AB,4);
for i := 0 to 3 do
AB[i] := i and 1=1;
test := dyn1.SaveToJSON;
check(test='[false,true,false,true]');
Check(AB<>nil);
dyn1.Clear;
Check(AB=nil);
Check(dyn1.Count=0);
Check(dyn1.LoadFromJSON(pointer(test))<>nil);
Check(length(AB)=4);
Check(dyn1.Count=4);
for i := 0 to 3 do
Check(AB[i]=(i and 1=1));
Test := dyn1.SaveTo;
dyn1.Clear;
Check(AB=nil);
Check(dyn1.LoadFrom(pointer(test))<>nil);
Check(dyn1.Count=4);
for i := 0 to 3 do
Check(AB[i]=(i and 1=1));
Check(dyniter.Init(TypeInfo(TBooleanDynArray),pointer(test)));
Check(dyniter.Count=4);
for i := 0 to 3 do begin
Check(dyniter.FirstField(B));
Check(B=(i and 1=1));
B := not B;
Check(dyniter.Step(B));
Check(B=(i and 1=1));
end;
Check(not dyniter.Step(B));
Check(not dyniter.FirstField(B));
Check(dyniter.CheckHash,'checkhash');
// validate TIntegerDynArray
Test64K;
AIP.Init(TypeInfo(TIntegerDynArray),AI);
for i := 0 to 1000 do begin
Check(AIP.Count=i);
Check(AIP.Add(i)=i);
Check(AIP.Count=i+1);
Check(AI[i]=i);
end;
for i := 0 to 1000 do
Check(AIP.IndexOf(i)=i);
for i := 0 to 1000 do begin
Check(IntegerScanExists(Pointer(AI),i+1,i));
Check(IntegerScanExists(Pointer(AI),AIP.Count,i));
Check(not IntegerScanExists(Pointer(AI),AIP.Count,i+2000));
end;
Test := AIP.SaveTo;
Check(Hash32(Test)=$924462C);
PI := IntegerDynArrayLoadFrom(pointer(Test),AIcount);
Check(AIcount=1001);
Check(PI<>nil);
for i := 0 to 1000 do
Check(PI[i]=i);
W.AddDynArrayJSON(AIP);
U := W.Text;
P := pointer(U);
for i := 0 to 1000 do
Check(GetNextItemCardinal(P)=cardinal(i));
Check(Hash32(U)=$CBDFDAFC,'hash32a');
for i := 0 to 1000 do begin
Test2 := AIP.ElemSave(i);
Check(length(Test2)=4);
k := 0;
AIP.ElemLoad(pointer(Test2),k);
Check(k=i);
Check(AIP.ElemLoadFind(pointer(Test2))=i);
end;
AIP.Reverse;
for i := 0 to 1000 do
Check(AI[i]=1000-i);
AIP.Clear;
Check(AIP.LoadFrom(pointer(Test))<>nil);
for i := 0 to 1000 do
Check(AIP.IndexOf(i)=i);
for i := 1000 downto 0 do
if i and 3=0 then
AIP.Delete(i);
Check(AIP.Count=750);
for i := 0 to 1000 do
if i and 3=0 then
Check(AIP.IndexOf(i)<0) else
Check(AIP.IndexOf(i)>=0);
AIP.Clear;
Check(AIP.LoadFromJSON(pointer(U))<>nil);
for i := 0 to 1000 do
Check(AI[i]=i);
AIP.Init(TypeInfo(TIntegerDynArray),AI,@AIcount);
for i := 0 to 50000 do begin
Check(AIP.Count=i,'use of AIcount should reset it to zero');
Check(AIP.Add(i)=i);
Check(AIP.Count=i+1);
Check(AI[i]=i);
end;
AIP.Compare := SortDynArrayInteger;
AIP.Sort;
Check(AIP.Count=50001);
for i := 0 to AIP.Count-1 do
Check(AIP.Find(i)=i);
Test := AIP.SaveTo;
Check(Hash32(Test)=$B9F2502A,'hash32b');
AIP.Reverse;
for i := 0 to 50000 do
Check(AI[i]=50000-i);
SetLength(AI,AIcount);
AIP.Init(TypeInfo(TIntegerDynArray),AI);
AIP.Compare := SortDynArrayInteger;
AIP.Sort;
Test := AIP.SaveTo;
Check(Hash32(Test)=$B9F2502A,'hash32c');
AIP.Reverse;
AIP.Slice(AI2,2000,1000);
Check(length(AI2)=2000);
for i := 0 to 1999 do
Check(AI2[i]=49000-i);
AIP.AddArray(AI2,1000,2000);
Check(AIP.Count=51001);
for i := 0 to 50000 do
Check(AI[i]=50000-i);
for i := 0 to 999 do
Check(AI[i+50001]=48000-i);
AIP.Count := 50001;
AIP.AddArray(AI2);
Check(AIP.Count=52001);
for i := 0 to 50000 do
Check(AI[i]=50000-i);
for i := 0 to 1999 do
Check(AI[i+50001]=49000-i);
AIP.Clear;
with DynArray(TypeInfo(TIntegerDynArray),AI) do begin
Check(LoadFrom(pointer(Test))<>nil);
for i := 0 to Count-1 do
Check(AI[i]=i);
end;
Check(AIP.Count=50001);
{$ifndef DELPHI5OROLDER}
AI2P.Init(TypeInfo(TIntegerDynArray),AI2);
AIP.AddDynArray(AI2P);
Check(AIP.Count=52001);
for i := 0 to 50000 do
Check(AI[i]=i);
for i := 0 to 1999 do
Check(AI[i+50001]=49000-i);
{$endif}
// validate TSynValidates (an array of classes is an array of PtrInt)
AVP.Init(TypeInfo(TSynValidates),AV);
for i := 0 to 1000 do begin
Check(AVP.Count=i);
PtrInt(V) := i;
Check(AVP.Add(V)=i);
Check(AVP.Count=i+1);
Check(AV[i]=V);
end;
Check(length(AV)=1001);
Check(AVP.Count=1001);
for i := 0 to 1000 do begin
// untyped const must be the same exact type !
PtrInt(V) := i;
Check(AVP.IndexOf(V)=i);
end;
Test := AVP.SaveTo;
Check(Hash32(Test)={$ifdef CPU64}$31484630{$else}$924462C{$endif},'hash32d');
// validate TRawUTF8DynArray
AUP.Init(TypeInfo(TRawUTF8DynArray),AU);
for i := 0 to 1000 do begin
Check(AUP.Count=i);
U := UInt32ToUtf8(i+1000);
Check(AUP.Add(U)=i);
Check(AUP.Count=i+1);
Check(AU[i]=U);
end;
for i := 0 to 1000 do begin
U := Int32ToUtf8(i+1000);
Check(AUP.IndexOf(U)=i);
end;
Test := AUP.SaveTo;
Check(Hash32(@Test[2],length(Test)-1)=$D9359F89,'hash32e'); // trim Test[1]=ElemSize
for i := 0 to 1000 do begin
U := Int32ToUtf8(i+1000);
Check(RawUTF8DynArrayLoadFromContains(pointer(Test),pointer(U),length(U),false)=i);
Check(RawUTF8DynArrayLoadFromContains(pointer(Test),pointer(U),length(U),true)=i);
end;
for i := 0 to 1000 do begin
U := UInt32ToUtf8(i+1000);
Test2 := AUP.ElemSave(U);
Check(length(Test2)>4);
U := '';
AUP.ElemLoad(pointer(Test2),U);
Check(GetInteger(pointer(U))=i+1000);
Check(AUP.ElemLoadFind(pointer(Test2))=i);
end;
W.CancelAll;
W.AddDynArrayJSON(AUP);
W.SetText(U);
Check(Hash32(U)=$1D682EF8,'hash32f');
P := pointer(U);
if not CheckFailed(P^='[') then inc(P);
for i := 0 to 1000 do begin
Check(P^='"'); inc(P);
Check(GetNextItemCardinal(P)=cardinal(i+1000));
if P=nil then
break;
end;
Check(P=nil);
AUP.Clear;
Check(AUP.LoadFrom(pointer(Test))-pointer(Test)=length(Test));
for i := 0 to 1000 do
Check(GetInteger(pointer(AU[i]))=i+1000);
Check(dyniter.Init(TypeInfo(TRawUTF8DynArray),pointer(test)));
Check(dyniter.Count=1001);
for i := 0 to 1000 do begin
Check(dyniter.FirstField(U2));
Check(GetInteger(pointer(U2))=i+1000);
U2 := '';
Check(dyniter.Step(U2));
Check(GetInteger(pointer(U2))=i+1000);
end;
Check(not dyniter.Step(U2));
Check(not dyniter.FirstField(U2));
Check(dyniter.CheckHash);
AUP.Clear;
Check(AUP.LoadFromJSON(pointer(U))<>nil);
for i := 0 to 1000 do
Check(GetInteger(pointer(AU[i]))=i+1000);
for i := 0 to 1000 do begin
U := Int32ToUtf8(i+1000);
Check(AUP.IndexOf(U)=i);
end;
for i := 1000 downto 0 do
if i and 3=0 then
AUP.Delete(i);
Check(AUP.Count=750);
for i := 0 to 1000 do begin
U := Int32ToUtf8(i+1000);
if i and 3=0 then
Check(AUP.IndexOf(U)<0) else
Check(AUP.IndexOf(U)>=0);
end;
U := 'inserted';
AUP.Insert(500,U);
Check(AUP.IndexOf(U)=500);
j := 0;
for i := 0 to AUP.Count-1 do
if i<>500 then begin
U := Int32ToUtf8(j+1000);
if j and 3=0 then
Check(AUP.IndexOf(U)<0) else
Check(AUP.IndexOf(U)>=0);
inc(j);
end;
AUP.CreateOrderedIndex(Index,SortDynArrayAnsiString);
Check(StrComp(pointer(AU[Index[750]]),pointer(AU[Index[749]]))>0);
for i := 1 to AUP.Count-1 do
Check(AU[Index[i]]>AU[Index[i-1]]);
AUP.Compare := SortDynArrayAnsiString;
AUP.Sort;
Check(AUP.Sorted);
Check(AU[AUP.Count-1]='inserted');
for i := 1 to AUP.Count-1 do
Check(AU[i]>AU[i-1]);
j := 0;
for i := 0 to AUP.Count-1 do
if i<>500 then begin
U := Int32ToUtf8(j+1000);
if j and 3=0 then
Check(AUP.Find(U)<0) else
Check(AUP.Find(U)>=0);
inc(j);
end;
AUP.Sorted := false;
j := 0;
for i := 0 to AUP.Count-1 do
if i<>500 then begin
U := Int32ToUtf8(j+1000);
if j and 3=0 then
Check(AUP.Find(U)<0) else
Check(AUP.Find(U)>=0);
inc(j);
end;
// validate packed binary record (no string inside)
ARP.Init(TypeInfo(TRecs),AR);
for i := 0 to 1000 do begin
Check(ARP.Count=i);
R.A := i;
R.B := i+1;
R.C := i*2.2;
R.D := i*3.25;
Check(ARP.Add(R)=i);
Check(ARP.Count=i+1);
end;
for i := 0 to 1000 do begin
with AR[i] do begin
Check(A=i);
Check(B=byte(i+1));
CheckSame(C,i*2.2);
CheckSame(D,i*3.25);
end;
R.A := i;
R.B := i+1;
R.C := i*2.2;
R.D := i*3.25;
Check(ARP.IndexOf(R)=i); // will work (packed + no ref-counted types inside)
end;
W.CancelAll;
W.AddDynArrayJSON(ARP);
U := W.Text;
// no check(Hash32(U)) since it is very platform-dependent: LoadFromJSON is enough
P := pointer(U);
JSON_BASE64_MAGIC_UTF8 := RawUnicodeToUtf8(@MAGIC,2);
U2 := RawUTF8('[')+JSON_BASE64_MAGIC_UTF8+RawUTF8(BinToBase64(ARP.SaveTo))+RawUTF8('"]');
Check(U=U2);
ARP.Clear;
Check(ARP.LoadFromJSON(pointer(U))<>nil);
if not CheckFailed(ARP.Count=1001) then
for i := 0 to 1000 do
with AR[i] do begin
Check(A=i);
Check(B=byte(i+1));
CheckSame(C,i*2.2);
CheckSame(D,i*3.25);
end;
// validate packed record with strings inside
AFP.Init(TypeInfo(TFVs),AF);
for i := 0 to 1000 do begin
Check(AFP.Count=i);
Fill(F,i);
Check(AFP.Add(F)=i);
Check(AFP.Count=i+1);
end;
Fill(F,100);
Check(RecordEquals(F,AF[100],TypeInfo(TFV)));
Len := RecordSaveLength(F,TypeInfo(TFV));
Check(Len=38{$ifdef UNICODE}+length(F.Main)+length(F.Detailed){$endif});
SetLength(Test,Len);
Check(RecordSave(F,pointer(Test),TypeInfo(TFV))-pointer(Test)=Len);
Fill(F,0);
Check(RecordLoad(F,pointer(Test),TypeInfo(TFV))-pointer(Test)=Len);
Check(RecordEquals(F,AF[100],TypeInfo(TFV)));
Test := RecordSaveBase64(F,TypeInfo(TFV));
Check(Test<>'');
Fill(F,0);
Check(RecordLoadBase64(pointer(Test),length(Test),F,TypeInfo(TFV)));
Check(RecordEquals(F,AF[100],TypeInfo(TFV)));
Test := RecordSaveBase64(F,TypeInfo(TFV),true);
Check(Test<>'');
Fill(F,0);
Check(RecordLoadBase64(pointer(Test),length(Test),F,TypeInfo(TFV),true));
Check(RecordEquals(F,AF[100],TypeInfo(TFV)));
for i := 0 to 1000 do
with AF[i] do begin
Check(Major=i);
Check(Minor=i+1);
Check(Release=i+2);
Check(Build=i+3);
Check(Main=IntToString(i+1000));
Check(Detailed=IntToString(2000-i));
CheckSame(BuildDateTime,36215.12);
Check(BuildYear=i+2011);
end;
for i := 0 to 1000 do begin
Fill(F,i);
Check(AFP.IndexOf(F)=i);
end;
Test := AFP.SaveTo;
Check(Hash32(Test)={$ifdef CPU64}{$ifdef FPC}$3DE22166{$else}$A29C10E{$endif}{$else}
{$ifdef UNICODE}$62F9C106{$else}$6AA2215E{$endif}{$endif},'hash32h');
for i := 0 to 1000 do begin
Fill(F,i);
AFP.ElemCopy(F,F1);
Check(AFP.ElemEquals(F,F1));
Test2 := AFP.ElemSave(F);
Check(length(Test2)>4);
AFP.ElemClear(F);
AFP.ElemLoad(pointer(Test2),F);
Check(AFP.ElemEquals(F,F1));
Check(AFP.ElemLoadFind(pointer(Test2))=i);
end;
W.CancelAll;
W.AddDynArrayJSON(AFP);
U := W.Text;
{$ifdef ISDELPHI2010} // thanks to enhanced RTTI
Check(IdemPChar(pointer(U),'[{"MAJOR":0,"MINOR":1,"RELEASE":2,"BUILD":3,'+
'"MAIN":"1000","DETAILED":"2000","BUILDDATETIME":"1999-02-24T02:52:48",'+
'"BUILDYEAR":2011},{"MAJOR":1,"MINOR":2,"RELEASE":3,"BUILD":4,'));
Check(Hash32(U)=$74523E0F,'hash32i');
{$else}
Check(U='['+JSON_BASE64_MAGIC_UTF8+BinToBase64(Test)+'"]');
{$endif}
AFP.Clear;
Check(AFP.LoadFrom(pointer(Test))-pointer(Test)=length(Test));
for i := 0 to 1000 do begin
Fill(F,i);
Check(AFP.IndexOf(F)=i);
end;
Check(dyniter.Init(TypeInfo(TFVs),pointer(test)));
Check(dyniter.Count=1001);
for i := 0 to 1000 do begin
Check(dyniter.Step(F1));
Fill(F,i);
Check(AFP.ElemEquals(F,F1));
end;
Check(not dyniter.Step(F1));
Check(dyniter.CheckHash);
ST := THeapMemoryStream.Create;
AFP.SaveToStream(ST);
AFP.Clear;
ST.Position := 0;
AFP.LoadFromStream(ST);
Check(ST.Position=length(Test));
for i := 0 to 1000 do begin
Fill(F,i);
Check(AFP.IndexOf(F)=i);
end;
ST.Free;
AFP.Clear;
Check(AFP.LoadFromJSON(pointer(U))<>nil);
for i := 0 to 1000 do begin
Fill(F,i);
Check(RecordEquals(F,AF[i],AFP.ElemType));
end;
for i := 0 to 1000 do begin
Fill(F,i);
F.BuildYear := 10;
Check(AFP.IndexOf(F)<0);
F.BuildYear := i+2011;
F.Detailed := '??';
Check(AFP.IndexOf(F)<0);
end;
for i := 1000 downto 0 do
if i and 3=0 then
AFP.Delete(i);
Check(AFP.Count=750);
for i := 0 to 1000 do begin
Fill(F,i);
if i and 3=0 then
Check(AFP.IndexOf(F)<0) else
Check(AFP.IndexOf(F)>=0);
end;
Fill(F,5000);
AFP.Insert(500,F);
Check(AFP.IndexOf(F)=500);
j := 0;
for i := 0 to AFP.Count-1 do
if i<>500 then begin
Fill(F,j);
if j and 3=0 then
Check(AFP.IndexOf(F)<0) else
Check(AFP.IndexOf(F)>=0);
inc(j);
end;
Finalize(Index);
AFP.CreateOrderedIndex(Index,FVSort);
for i := 1 to AUP.Count-1 do
Check(AF[Index[i]].Detailed>AF[Index[i-1]].Detailed);
AFP.Compare := FVSort;
AFP.Sort;
for i := 1 to AUP.Count-1 do
Check(AF[i].Detailed>AF[i-1].Detailed);
j := 0;
for i := 0 to AFP.Count-1 do
if i<>500 then begin
Fill(F,j);
if j and 3=0 then
Check(AFP.Find(F)<0) else
Check(AFP.Find(F)>=0);
inc(j);
end;
W.Free;
// validate packed record with records of strings inside
AFP.Init(Typeinfo(TFV2s),AF2);
for i := 0 to 1000 do begin
Fill(F2.V1,i*2);
F2.Value := i;
Fill(F2.V2,i*2+1);
F2.Text := IntToString(i);
Check(AFP.Add(F2)=i);
end;
Check(AFP.Count=1001);
TestAF2;
Test := AFP.SaveTo;
AFP.Clear;
Check(AFP.Count=0);
Check(AFP.LoadFrom(pointer(Test))<>nil);
Check(AFP.Count=1001);
TestAF2;
// validate https://synopse.info/forum/viewtopic.php?pid=16581#p16581
DP.Modified := Now;
DP.Data := '1';
dyn1.Init(TypeInfo(TDataItems),dyn1Array);
dyn1.Add(DP);
DP.Modified := Now;
DP.Data := '2';
dyn2.Init(TypeInfo(TDataItems),dyn2Array);
check(dyn2.count=0);
dyn2.Add(DP);
check(length(dyn2Array)=1);
check(dyn2.count=1);
dyn2.AddArray(dyn1Array);
check(dyn2.count=2);
check(dyn2.ElemEquals(dyn2Array[0],DP));
check(dyn2.ElemEquals(dyn2Array[1],dyn1Array[0]));
{$ifndef DELPHI5OROLDER}
dyn2.AddDynArray(dyn1);
check(dyn2.count=3);
check(dyn2.ElemEquals(dyn2Array[0],DP));
check(dyn2.ElemEquals(dyn2Array[1],dyn1Array[0]));
check(dyn2.ElemEquals(dyn2Array[2],dyn1Array[0]));
{$endif}
// valide generic-like features
// see http://docwiki.embarcadero.com/CodeExamples/en/Generics_Collections_TDictionary_(Delphi)
ACities.Init(TypeInfo(TCityDynArray),Province.Cities);
City.Name := 'Iasi';
City.Country := 'Romania';
City.Latitude := 47.16;
City.Longitude := 27.58;
ACities.Add(City);
City.Name := 'London';
City.Country := 'United Kingdom';
City.Latitude := 51.5;
City.Longitude := -0.17;
ACities.Add(City);
City.Name := 'Buenos Aires';
City.Country := 'Argentina';
City.Latitude := 0;
City.Longitude := 0;
ACities.Add(City);
Check(ACities.Count=3);
ACities.Compare := SortDynArrayString; // will search by Name = 1st field
City.Name := 'Iasi';
Check(ACities.FindAndFill(City)=0);
Check(City.Name='Iasi');
Check(City.Country='Romania');
CheckSame(City.Latitude,47.16);
CheckSame(City.Longitude,27.58);
Check(ACities.FindAndDelete(City)=0);
Check(City.Name='Iasi');
Check(ACities.Find(City)<0);
City.Name := 'Buenos Aires';
City.Country := 'Argentina';
City.Latitude := -34.6;
City.Longitude := -58.45;
Check(ACities.FindAndUpdate(City)>=0);
City.Latitude := 0;
City.Longitude := 0;
Check(City.Name='Buenos Aires');
Check(ACities.FindAndFill(City)>=0);
CheckSame(City.Latitude,-34.6);
CheckSame(City.Longitude,-58.45);
Check(ACities.FindAndAddIfNotExisting(City)>=0);
City.Name := 'Iasi';
City.Country := 'Romania';
City.Latitude := 47.16;
City.Longitude := 27.58;
Check(ACities.FindAndAddIfNotExisting(City)<0);
Check(City.Name='Iasi');
Check(ACities.FindAndUpdate(City)>=0);
ACities.Sort;
for i := 1 to high(Province.Cities) do
Check(Province.Cities[i].Name>Province.Cities[i-1].Name);
Check(ACities.Count=3);
// complex record test
Province.Name := 'Test';
Province.Comment := 'comment';
Province.Year := 1000;
Test := RecordSave(Province,TypeInfo(TProvince));
RecordClear(Province,TypeInfo(TProvince));
Check(Province.Name='');
Check(Province.Comment='');
Check(length(Province.Cities)=0);
Check(ACities.Count=0);
Province.Year := 0;
Check(RecordLoad(Province,pointer(Test),TypeInfo(TProvince))^=#0);
Check(Province.Name='Test');
Check(Province.Comment='comment');
Check(Province.Year=1000);
Check(length(Province.Cities)=3);
Check(ACities.Count=3);
for i := 1 to high(Province.Cities) do
Check(Province.Cities[i].Name>Province.Cities[i-1].Name);
// big array test
ACities.Init(TypeInfo(TCityDynArray),Province.Cities);
ACities.Clear;
for i := 0 to 10000 do begin
City.Name := IntToString(i);
City.Latitude := i*3.14;
City.Longitude := i*6.13;
Check(ACities.Add(City)=i);
end;
Check(ACities.Count=Length(Province.Cities));
Check(ACities.Count=10001);
TestCities;
ACities.Init(TypeInfo(TCityDynArray),Province.Cities,@count);
ACities.Clear;
for i := 0 to 100000 do begin
City.Name := IntToString(i);
City.Latitude := i*3.14;
City.Longitude := i*6.13;
Check(ACities.Add(City)=i);
end;
Check(ACities.Count=count);
TestCities;
end;
procedure TTestLowLevelCommon.SystemCopyRecord;
type TR = record
One: integer;
S1: AnsiString;
Three: byte;
S2: WideString;
Five: boolean;
{$ifndef NOVARIANTS}
V: Variant;
{$endif}
R: Int64Rec;
Arr: array[0..10] of AnsiString;
Dyn: array of integer;
Bulk: array[0..19] of byte;
end;
var A,B,C: TR;
i: integer;
begin
FillCharFast(A,sizeof(A),0);
for i := 0 to High(A.Bulk) do
A.Bulk[i] := i;
A.S1 := 'one';
A.S2 := 'two';
A.Five := true;
A.Three := $33;
{$ifndef NOVARIANTS}
A.V := 'One Two';
{$endif}
A.R.Lo := 10;
A.R.Hi := 20;
A.Arr[5] := 'five';
SetLength(A.Dyn,10);
A.Dyn[9] := 9;
B := A;
Check(A.One=B.One);
Check(A.S1=B.S1);
Check(A.Three=B.Three);
Check(A.S2=B.S2);
Check(A.Five=B.Five);
{$ifndef NOVARIANTS}
Check(A.V=B.V);
{$endif}
Check(Int64(A.R)=Int64(B.R));
Check(A.Arr[5]=B.Arr[5]);
Check(A.Arr[0]=B.Arr[0]);
Check(A.Dyn[9]=B.Dyn[9]);
Check(A.Dyn[0]=0);
for i := 0 to High(B.Bulk) do
Check(B.Bulk[i]=i);
for i := 0 to High(B.Bulk) do
Check(CompareMem(@A.Bulk,@B.Bulk,i));
FillCharFast(A.Bulk,sizeof(A.Bulk),255);
for i := 0 to High(B.Bulk) do
Check(CompareMem(@A.Bulk,@B.Bulk,i)=(i=0));
B.Three := 3;
B.Dyn[0] := 10;
C := B;
Check(A.One=C.One);
Check(A.S1=C.S1);
Check(C.Three=3);
Check(A.S2=C.S2);
Check(A.Five=C.Five);
{$ifndef NOVARIANTS}
Check(A.V=C.V);
{$endif}
Check(Int64(A.R)=Int64(C.R));
Check(A.Arr[5]=C.Arr[5]);
Check(A.Arr[0]=C.Arr[0]);
Check(A.Dyn[9]=C.Dyn[9]);
{Check(A.Dyn[0]=0) bug in original VCL?}
Check(C.Dyn[0]=10);
end;
procedure TTestLowLevelCommon.UrlEncoding;
var i,j: integer;
s: RawByteString;
name,value,utf: RawUTF8;
P: PUTF8Char;
GUID2: TGUID;
U: TURI;
const GUID: TGUID = '{c9a646d3-9c61-4cb7-bfcd-ee2522c8f633}';
procedure Test(const decoded,encoded: RawUTF8);
begin
Check(UrlEncode(decoded)=encoded);
Check(UrlDecode(encoded)=decoded);
Check(UrlDecode(PUTF8Char(encoded))=decoded);
end;
begin
Test('abcdef','abcdef');
Test('abcdefyzABCDYZ01239_-.~ ','abcdefyzABCDYZ01239_-.~+');
Test('"Aardvarks lurk, OK?"','%22Aardvarks+lurk%2C+OK%3F%22');
Test('"Aardvarks lurk, OK%"','%22Aardvarks+lurk%2C+OK%25%22');
Test('where=name like :(''Arnaud%'')','where%3Dname+like+%3A%28%27Arnaud%25%27%29');
Check(UrlDecode('where=name%20like%20:(%27Arnaud%%27):')=
'where=name like :(''Arnaud%''):','URI from browser');
P := UrlDecodeNextNameValue('where=name+like+%3A%28%27Arnaud%25%27%29%3A',
name,value);
Check(P<>nil);
Check(P^=#0);
Check(name='where');
Check(value='name like :(''Arnaud%''):');
P := UrlDecodeNextNameValue('where%3Dname+like+%3A%28%27Arnaud%25%27%29%3A',
name,value);
Check(P<>nil);
Check(P^=#0);
Check(name='where');
Check(value='name like :(''Arnaud%''):');
P := UrlDecodeNextNameValue('where%3Dname+like+%3A%28%27Arnaud%%27%29%3A',
name,value);
Check(P<>nil);
Check(P^=#0);
Check(name='where');
Check(value='name like :(''Arnaud%''):','URI from browser');
P := UrlDecodeNextNameValue('name%2Ccom+plex=value',name,value);
Check(P<>nil);
Check(P^=#0);
Check(name='name,com plex');
Check(value='value');
P := UrlDecodeNextNameValue('name%2Ccomplex%3Dvalue',name,value);
Check(P<>nil);
Check(P^=#0);
Check(name='name,complex');
Check(value='value');
for i := 0 to 100 do begin
j := i*5; // circumvent weird FPC code generation bug in -O2 mode
s := RandomString(j);
Check(UrlDecode(UrlEncode(s))=s,string(s));
end;
utf := BinToBase64URI(@GUID,sizeof(GUID));
Check(utf='00amyWGct0y_ze4lIsj2Mw');
FillCharFast(GUID2,sizeof(GUID2),0);
Check(Base64uriToBin(utf,@GUID2,SizeOf(GUID2)));
Check(IsEqualGUID(GUID2,GUID));
Check(U.From('toto.com'));
Check(U.URI='http://toto.com/');
Check(U.From('toto.com:123'));
Check(U.URI='http://toto.com:123/');
Check(U.From('https://toto.com:123/tata/titi'));
Check(U.URI='https://toto.com:123/tata/titi');
Check(U.From('https://toto.com:123/tata/tutu:tete'));
Check(U.URI='https://toto.com:123/tata/tutu:tete');
Check(U.From('toto.com/tata/tutu:tete'));
Check(U.URI='http://toto.com/tata/tutu:tete');
end;
procedure TTestLowLevelCommon._GUID;
var i: integer;
s: RawByteString;
st: string;
g,g2: TGUID;
const GUID: TGUID = '{c9a646d3-9c61-4cb7-bfcd-ee2522c8f633}';
begin
s := GUIDToRawUTF8(GUID);
Check(s='{C9A646D3-9C61-4CB7-BFCD-EE2522C8F633}');
Check(TextToGUID(@s[2],@g2)^='}');
Check(IsEqualGUID(g2,GUID));
Check(GUIDToString(GUID)='{C9A646D3-9C61-4CB7-BFCD-EE2522C8F633}');
Check(IsEqualGUID(RawUTF8ToGUID(s),GUID));
for i := 1 to 1000 do begin
g.D1 := Random(maxInt);
g.D2 := Random(65535);
g.D3 := Random(65535);
Int64(g.D4) := Int64(Random(maxInt))*Random(maxInt);
st := GUIDToString(g);
{$ifndef DELPHI5OROLDER}
Check(st=SysUtils.GUIDToString(g));
{$endif}
Check(IsEqualGUID(StringToGUID(st),g));
s := GUIDToRawUTF8(g);
Check(st=UTF8ToString(s));
st[Random(38)+1] := ' ';
g2 := StringToGUID(st);
Check(IsZero(@g2,sizeof(g2)));
Check(TextToGUID(@s[2],@g2)^='}');
Check(IsEqualGUID(g2,g));
Check(IsEqualGUID(RawUTF8ToGUID(s),g));
inc(g.D1);
Check(not IsEqualGUID(g2,g));
Check(not IsEqualGUID(RawUTF8ToGUID(s),g));
end;
{$ifdef ISDELPHI2010}
s := RecordSaveJSON(g,TypeInfo(TGUID));
FillCharFast(g2,sizeof(g2),0);
Check(RecordLoadJSON(g2,pointer(s),TypeInfo(TGUID))<>nil);
Check(IsEqualGUID(g2,g));
{$endif}
end;
procedure TTestLowLevelCommon._IsMatch;
var i: integer;
V: RawUTF8;
match: TMatch;
reuse: boolean;
begin
Check(IsMatch('','',true));
Check(not IsMatch('','toto',true));
Check(not IsMatch('Bidule.pas','',true));
Check(IsMatch('Bidule.pas','Bidule.pas',true));
Check(IsMatch('Bidule.pas','BIDULE.pas',true));
Check(IsMatch('Bidule.pas','Bidule.paS',true));
Check(IsMatch('Bidule.pas','Bidule.pas',false));
Check(not IsMatch('Bidule.pas','bidule.pas',false));
Check(not IsMatch('bidule.pas','bidulE.pas',false));
Check(not IsMatch('bidule.pas','bidule.paS',false));
Check(not IsMatch('bidule.pas','bidule.pa',false));
for i := 0 to 200 do begin
V := Int32ToUtf8(i);
Check(IsMatch(V,V,false)=IsMatch(V,V,true));
end;
Check(IsMatch('test*','test',false));
Check(IsMatch('test*','test',true));
Check(IsMatch('test*','teste',false));
Check(IsMatch('test*','teste',true));
Check(IsMatch('test*','tester',false));
Check(IsMatch('test*','tester',true));
Check(IsMatch('a*','anything',true));
Check(IsMatch('a*','a',true));
Check(IsMatch('*','anything',true));
Check(IsMatch('*.pas','Bidule.pas',true));
Check(IsMatch('*.pas','Bidule.pas',false));
Check(IsMatch('*.PAS','Bidule.pas',true));
Check(not IsMatch('*.PAS','Bidule.pas',false));
Check(IsMatch('*.p?s','Bidule.pas',true));
Check(IsMatch('*.p*S','Bidule.pas',true));
Check(IsMatch('B*.PAS','bidule.pas',true));
Check(IsMatch('*.p?s','bidule.pas',false));
Check(IsMatch('*.p*s','bidule.pas',false));
Check(IsMatch('b*.pas','bidule.pas',false));
Check(not IsMatch('B*.das','Bidule.pas',true));
Check(IsMatch('bidule.*','Bidule.pas',true));
Check(IsMatch('ma?ch.*','match.exe',false));
Check(IsMatch('ma?ch.*','mavch.dat',false));
Check(IsMatch('ma?ch.*','march.on',false));
Check(IsMatch('ma?ch.*','march.',false));
Check(IsMatch('ab*.exyz', 'ab.exyz',true));
Check(IsMatch('ab[ef]xyz', 'abexyz',false));
Check(IsMatch('ab[ef]xyz', 'abexyz',true));
Check(IsMatch('ab*.[ef]xyz', 'abcd.exyz',true));
Check(IsMatch('ab*.[ef]xyz', 'ab.exyz',true));
Check(IsMatch('ab*.[ef]xyz', 'abcd.exyz',true));
Check(IsMatch('ab*.[ef]xyz', 'ab.fxyz',true));
Check(IsMatch('ab*.[ef]xyz', 'abcd.fxyz',true));
check(not IsMatch('ab[cd]e','abdde',false));
check(not IsMatch('ab[cd]ex','abddex',false));
check(not IsMatch('ab*.[cd]e','ab.dde',false));
check(not IsMatch('ab*.[cd]ex','ab.ddex',false));
V := 'this [e-n]s a [!zy]est';
check(not IsMatch(V,V,false));
Check(IsMatch(V,'this is a test',false));
Check(IsMatch(V,'this is a rest',false));
Check(not IsMatch(V,'this is a zest',false));
Check(not IsMatch(V,'this as a test',false));
Check(not IsMatch(V,'this as a rest',false));
for reuse := false to true do begin // ensure very same behavior
match.Prepare(V, false, reuse);
Check(not match.Match(V));
Check(match.Match('this is a test'));
Check(match.Match('this is a rest'));
Check(not match.Match('this is a zest'));
match.Prepare('test', false, reuse);
check(match.Match('test'));
check(not match.Match('tes'));
check(not match.Match('tests'));
check(not match.Match('tesT'));
match.Prepare('teST', true, reuse);
check(match.Match('test'));
check(match.Match('test'));
match.Prepare('*', false, reuse);
check(match.Match('test'));
check(match.Match('tests'));
match.Prepare('*', true, reuse);
check(match.Match('test'));
check(match.Match('tests'));
match.Prepare('**', false, reuse);
check(match.Match('test'));
check(match.Match('tests'));
match.Prepare('****', false, reuse);
check(match.Match('test'));
check(match.Match('tests'));
match.Prepare('*.*', false, reuse);
check(match.Match('te.st'));
check(match.Match('te.st.'));
check(match.Match('test.'));
check(match.Match('.test'));
check(match.Match('.'));
check(not match.Match('test'));
match.Prepare('*.*', true, reuse);
check(match.Match('te.st'));
check(match.Match('te.st.'));
check(match.Match('test.'));
check(match.Match('.test'));
check(not match.Match('test'));
check(match.Match('.'));
match.Prepare('test*', false, reuse);
check(match.Match('test'));
check(match.Match('tests'));
check(match.Match('tester'));
check(not match.Match('atest'));
check(not match.Match('tes'));
check(not match.Match('tEst'));
check(not match.Match('tesT'));
check(not match.Match('t'));
match.Prepare('*test', false, reuse);
check(match.Match('test'));
check(match.Match('stest'));
check(match.Match('attest'));
check(not match.Match('est'));
check(not match.Match('testa'));
check(not match.Match('tes'));
check(not match.Match('tEst'));
check(not match.Match('tesT'));
check(not match.Match('t'));
match.Prepare('*t', false, reuse);
check(match.Match('t'));
check(match.Match('st'));
check(match.Match('tt'));
check(match.Match('att'));
check(not match.Match('s'));
check(not match.Match('es'));
check(not match.Match('ts'));
match.Prepare('**', false, reuse);
check(match.Match('') = reuse);
check(match.Match('test'));
match.Prepare('*test*', false, reuse);
check(match.Match('test'));
check(match.Match('tests'));
check(match.Match('tester'));
check(match.Match('atest'));
check(match.Match('ateste'));
check(match.Match('abtest'));
check(match.Match('abtester'));
check(not match.Match('tes'));
check(not match.Match('ates'));
check(not match.Match('tesates'));
check(not match.Match('tesT'));
check(not match.Match('Teste'));
check(not match.Match('TEster'));
check(not match.Match('atEst'));
check(not match.Match('ateSTe'));
match.Prepare('*12*', false, reuse);
check(match.Match('12'));
check(match.Match('12e'));
check(match.Match('12er'));
check(match.Match('a12'));
check(match.Match('a12e'));
check(match.Match('ab12'));
check(match.Match('ab12er'));
check(not match.Match('1'));
check(not match.Match('a1'));
check(not match.Match('1a2'));
match.Prepare('*teSt*', true, reuse);
check(match.Match('test'));
check(match.Match('teste'));
check(match.Match('tester'));
check(match.Match('atest'));
check(match.Match('ateste'));
check(match.Match('abtest'));
check(match.Match('abtester'));
check(match.Match('tesT'));
check(match.Match('Teste'));
check(match.Match('TEster'));
check(match.Match('atEst'));
check(match.Match('ateSTe'));
check(match.Match('abteST'));
check(match.Match('abtEster'));
check(not match.Match('tes'));
check(not match.Match('ates'));
check(not match.Match('tesates'));
match.Prepare('*te?t*', true, reuse);
check(match.Match('test'));
check(match.Match('tezt'));
check(match.Match('teste'));
check(match.Match('tezte'));
check(match.Match('tester'));
check(match.Match('atest'));
check(match.Match('ateste'));
check(not match.Match('tes'));
check(not match.Match('tet'));
check(not match.Match('ates'));
check(not match.Match('tesates'));
match.Prepare('?est*', true, reuse);
check(match.Match('test'));
check(match.Match('test'));
check(match.Match('teste'));
check(match.Match('tester'));
check(not match.Match('tezte'));
check(not match.Match('atest'));
check(not match.Match('est'));
check(not match.Match('este'));
check(not match.Match('tes'));
check(not match.Match('tet'));
check(not match.Match('ates'));
check(not match.Match('tesates'));
match.Prepare('a*bx*cy*d', false, reuse);
check(match.Match('abxcyd'));
check(match.Match('a1bxcyd'));
check(match.Match('a12bxcyd'));
check(match.Match('a123bxcyd'));
check(match.Match('abx1cyd'));
check(match.Match('abx12cyd'));
check(match.Match('abxcy1d'));
check(match.Match('abxcy12d'));
check(match.Match('abxcy123d'));
check(not match.Match('abcyd'));
check(not match.Match('abxcyde'));
match.Prepare('************************************************'+
'************************************************'+
'**************************************************.*', false, reuse);
check(match.MatchThreadSafe('abxcyd.'));
check(match.MatchThreadSafe('abxc.yd'));
check(match.MatchThreadSafe('abxcy.d'));
check(match.MatchThreadSafe('.'));
check(match.MatchThreadSafe('.a'));
check(match.MatchThreadSafe('.abxcyd'));
check(not match.MatchThreadSafe('abxcyd'));
end;
for i := 32 to 127 do begin
SetLength(V,1);
V[1] := AnsiChar(i);
Check(IsMatch('[A-Za-z0-9]',V)=(i in IsWord));
Check(IsMatch('[01-456a-zA-Z789]',V)=(i in IsWord));
SetLength(V,3);
V[1] := AnsiChar(i);
V[2] := AnsiChar(i);
V[3] := AnsiChar(i);
Check(IsMatch('[A-Za-z0-9]?[A-Za-z0-9]',V)=(i in IsWord));
Check(IsMatch('[A-Za-z0-9]*',V)=(i in IsWord));
Check(IsMatch('[a-z0-9]?[A-Z0-9]',V,true)=(i in IsWord));
Check(IsMatch('[A-Z0-9]*',V,true)=(i in IsWord));
end;
end;
procedure TTestLowLevelCommon._Random32;
var i: integer;
c: array[0..1000] of cardinal;
begin
for i := 0 to high(c) do
c[i] := Random32;
QuickSortInteger(@c,0,high(c));
for i := 1 to high(c) do
Check(c[i+1]<>c[i],'unique Random32');
Check(Random32(0)=0);
for i := 1 to 100000 do
Check(Random32(i)<cardinal(i));
for i := 0 to 100000 do
Check(Random32(maxInt-i)<cardinal(maxInt-i));
end;
procedure TTestLowLevelCommon._TRawUTF8Interning;
var int: TRawUTF8Interning;
i,v: integer;
tmp: RawUTF8;
vs: TRawUTF8DynArray;
timer: TPrecisionTimer;
const MAX=500000;
DIRSIZE = 16*(MAX+1); // assume each SmallUInt32UTF8[] uses 16 heap bytes
INTSIZE = 512*16;
begin
{$ifndef HASINLINE} // inlining induces optimizations which trigger Clean
int := TRawUTF8Interning.Create(1);
try
check(int.Count=0);
check(int.Unique('test')='test');
check(int.Count=1);
check(int.Unique('test')='test');
check(int.Count=1);
check(int.Clean=0);
check(int.Unique('single')='single');
check(int.Count=2);
check(int.Clean=1);
check(int.Count=1);
check(int.Clean=0);
check(int.Count=1);
check(int.Unique('single1')='single1');
check(int.Count=2);
check(int.Unique('test2')='test2');
check(int.Count=3);
check(int.Unique('test2')='test2');
check(int.Count=3);
check(int.Unique('single2')='single2');
check(int.Count=4);
check(int.Clean=2);
check(int.Count=2);
int.Clear;
check(int.Count=0);
check(int.Clean=0);
check(int.Count=0);
finally
int.Free;
end;
{$endif HASINLINE}
int := TRawUTF8Interning.Create(16);
try
for i := 0 to MAX do begin
v := i and 511;
int.Unique(tmp,SmallUInt32UTF8[v]);
check(UTF8ToInteger(tmp)=v);
end;
check(int.Count=512);
check(int.Clean=0);
check(int.Count=512);
finally
int.Free;
end;
int := TRawUTF8Interning.Create(4);
try
SetLength(vs,MAX+1);
timer.Start;
for i := 0 to MAX do begin
v := i and 511;
int.Unique(vs[i],pointer(SmallUInt32UTF8[v]),length(SmallUInt32UTF8[v]));
end;
NotifyTestSpeed(Format('interning %s',[KB(INTSIZE)]),MAX,DIRSIZE,@timer);
for i := 0 to MAX do
check(UTF8ToInteger(vs[i])=i and 511);
check(int.Count=512);
check(int.Clean=0);
check(int.Count=512);
for i := 0 to MAX do
check(UTF8ToInteger(vs[i])=i and 511);
vs := nil;
check(int.Count=512);
check(int.Clean=512);
check(int.Count=0);
finally
int.Free;
end;
SetLength(vs,MAX+1);
timer.Start;
for i := 0 to MAX do begin
v := i and 511;
FastSetString(vs[i],pointer(SmallUInt32UTF8[v]),length(SmallUInt32UTF8[v]));
end;
NotifyTestSpeed(Format('direct %s',[KB(DIRSIZE)]),MAX,DIRSIZE,@timer);
for i := 0 to MAX do
check(UTF8ToInteger(vs[i])=i and 511);
end;
function kr32reference(buf: PAnsiChar; len: cardinal): cardinal;
var i: integer;
begin
result := 0;
for i := 0 to len-1 do
result := result*31+ord(buf[i]);
end;
function fnv32reference(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal;
var i: integer;
begin
for i := 0 to len-1 do
crc := (crc xor ord(buf[i]))*16777619;
result := crc;
end;
function crc32creference(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal;
begin
result := not crc;
if buf<>nil then
while len>0 do begin
result := crc32ctab[0,byte(result xor ord(buf^))] xor (result shr 8);
dec(len);
inc(buf);
end;
result := not result;
end;
{$ifndef FPC} // RolDWord is an intrinsic function under FPC :)
function RolDWord(value: cardinal; count: integer): cardinal;
{$ifdef HASINLINE}inline;{$endif}
begin
result := (value shl count) or (value shr (32-count));
end;
{$endif FPC}
function xxHash32reference(P: PAnsiChar; len: integer; seed: cardinal = 0): cardinal;
const
PRIME32_1 = 2654435761;
PRIME32_2 = 2246822519;
PRIME32_3 = 3266489917;
PRIME32_4 = 668265263;
PRIME32_5 = 374761393;
var c1, c2, c3, c4: cardinal;
PLimit, PEnd: PAnsiChar;
begin
PEnd := P + len;
if len >= 16 then
begin
PLimit := PEnd - 16;
c1 := seed + PRIME32_1 + PRIME32_2;
c2 := seed + PRIME32_2;
c3 := seed;
c4 := seed - PRIME32_1;
repeat
c1 := PRIME32_1 * RolDWord(c1 + PRIME32_2 * PCardinal(P)^, 13);
c2 := PRIME32_1 * RolDWord(c2 + PRIME32_2 * PCardinal(P+4)^, 13);
c3 := PRIME32_1 * RolDWord(c3 + PRIME32_2 * PCardinal(P+8)^, 13);
c4 := PRIME32_1 * RolDWord(c4 + PRIME32_2 * PCardinal(P+12)^, 13);
inc(P, 16);
until not (P <= PLimit);
result := RolDWord(c1, 1) + RolDWord(c2, 7) + RolDWord(c3, 12) + RolDWord(c4, 18);
end else
result := seed + PRIME32_5;
inc(result, len);
while P <= PEnd - 4 do begin
inc(result, PCardinal(P)^ * PRIME32_3);
result := RolDWord(result, 17) * PRIME32_4;
inc(P, 4);
end;
while P < PEnd do begin
inc(result, PByte(P)^ * PRIME32_5);
result := RolDWord(result, 11) * PRIME32_1;
inc(P);
end;
result := result xor (result shr 15);
result := result * PRIME32_2;
result := result xor (result shr 13);
result := result * PRIME32_3;
result := result xor (result shr 16);
end;
{$ifdef CPUINTEL}
procedure crcblockreference(crc128, data128: PBlock128);
var c: cardinal;
begin
c := crc128^[0] xor data128^[0];
crc128^[0] := crc32ctab[3,byte(c)] xor crc32ctab[2,byte(c shr 8)]
xor crc32ctab[1,byte(c shr 16)] xor crc32ctab[0,c shr 24];
c := crc128^[1] xor data128^[1];
crc128^[1] := crc32ctab[3,byte(c)] xor crc32ctab[2,byte(c shr 8)]
xor crc32ctab[1,byte(c shr 16)] xor crc32ctab[0,c shr 24];
c := crc128^[2] xor data128^[2];
crc128^[2] := crc32ctab[3,byte(c)] xor crc32ctab[2,byte(c shr 8)]
xor crc32ctab[1,byte(c shr 16)] xor crc32ctab[0,c shr 24];
c := crc128^[3] xor data128^[3];
crc128^[3] := crc32ctab[3,byte(c)] xor crc32ctab[2,byte(c shr 8)]
xor crc32ctab[1,byte(c shr 16)] xor crc32ctab[0,c shr 24];
end;
{$endif CPUINTEL}
procedure TTestLowLevelCommon._crc32c;
var crc: array[0..10000] of record
s: RawByteString;
crc: cardinal;
end;
totallen: Cardinal;
procedure Test(hash: THasher; const name: string);
var i: Integer;
Timer: TPrecisionTimer;
a: string[10];
begin
Timer.Start;
a := '123456789';
Check(hash(0,@a,0)=0);
Check(hash(0,@a,1)=$2ACF889D);
Check(hash(0,@a,2)=$BD5FE6AF);
Check(hash(0,@a,3)=$7F40BC73);
Check(hash(0,@a,4)=$13790E51);
Check(crc32cBy4(cardinal(not 0),PCardinal(@a)^)=cardinal(not $13790E51),'crc32cBy4');
Check(hash(0,@a,5)=$659AD21);
Check(hash(0,@a,6)=$85BF5A8C);
Check(hash(0,@a,7)=$8B0FB6FA);
Check(hash(0,@a,8)=$2E5336F0);
for i := 0 to High(crc) do
with crc[i] do
Check(hash(0,pointer(s),length(s))=crc);
Timer.ComputeTime;
fRunConsole := format('%s %s %s/s',[fRunConsole,name,KB(Timer.PerSec(totallen))]);
end;
procedure test16(const text: RawUTF8; expected: cardinal);
begin
Check(crc16(pointer(text),length(text))=expected);
end;
var i,j: integer;
Timer: TPrecisionTimer;
c1,c2: cardinal;
crc1,crc2: THash128;
digest: THash256;
tmp: RawByteString;
hmac32: THMAC_CRC32C;
// hmac256: THMAC_CRC256C;
begin
test16('',$ffff);
test16('a',$9d77);
test16('ab',$69f0);
test16('toto',$e2ca);
test16('123456789',$29b1);
test16('123456789123456789',$a86d);
totallen := 36;
tmp := '123456789123456789';
c2 := $12345678;
c1 := HMAC_CRC32C(@c2,pointer(tmp),4,length(tmp));
check(c1=$1C3C4B51);
hmac32.Init(@c2,4);
hmac32.Update(pointer(tmp),length(tmp));
check(hmac32.Done=c1);
c2 := $12345678;
HMAC_CRC256C(@c2,pointer(tmp),4,length(tmp),digest);
check(SHA256DigestToString(digest)='46da01fb9f4a97b5f8ba2c70512bc22aa'+
'a9b57e5030ced9f5c7c825ab5ec1715');
FillZero(crc2);
crcblock(@crc2,PBlock128(PAnsiChar('0123456789012345')));
check(not IsZero(crc2));
check(TBlock128(crc2)[0]=1314793854);
check(TBlock128(crc2)[1]=582109780);
check(TBlock128(crc2)[2]=1177891908);
check(TBlock128(crc2)[3]=4047040040);
{$ifdef CPUINTEL}
FillZero(crc1);
crcblockreference(@crc1,PBlock128(PAnsiChar('0123456789012345')));
check(not IsZero(crc1));
check(IsEqual(crc1,crc2));
FillZero(crc1);
crcblockNoSSE42(@crc1,PBlock128(PAnsiChar('0123456789012345')));
check(not IsZero(crc1));
check(IsEqual(crc1,crc2));
{$endif}
for i := 0 to 50000 do begin
FillZero(crc1);
crcblock(@crc1,@digest);
check(not IsZero(crc1));
{$ifdef CPUINTEL}
FillZero(crc2);
crcblockreference(@crc2,@digest);
check(not IsZero(crc2));
check(IsEqual(crc1,crc2));
FillZero(crc2);
crcblockNoSSE42(@crc2,@digest);
check(not IsZero(crc2));
check(IsEqual(crc1,crc2));
{$endif}
for j := 0 to high(digest) do
inc(digest[j]);
end;
for i := 0 to High(crc) do
with crc[i] do begin
j := i shr 3+1; // circumvent weird FPC code generation bug in -O2 mode
s := RandomString(j);
crc := crc32creference(0,pointer(s),length(s));
inc(totallen,length(s));
c2 := HMAC_CRC32C(@c1,pointer(s),4,length(s));
hmac32.Init(@c1,4);
hmac32.Update(pointer(s),length(s));
check(hmac32.Done=c2);
end;
Test(crc32creference,'pas');
Test(crc32cfast,'fast');
{$ifdef CPUINTEL}
if cfSSE42 in CpuFeatures then
Test(crc32csse42,'sse42');
{$ifdef CPUX64}
if (cfSSE42 in CpuFeatures) and (cfAesNi in CpuFeatures) then
Test(crc32c,'sse42+aesni'); // use SSE4.2+pclmulqdq instructions on x64
{$endif}
{$endif}
exit; // code below is speed informative only, without any test
Timer.Start;
for i := 0 to high(crc) do
with crc[i] do
fnv32(0,pointer(s),length(s));
fRunConsole := format('%s fnv32 %s %s/s',[fRunConsole,Timer.Stop,
KB(Timer.PerSec(totallen))]);
end;
procedure TTestLowLevelCommon.intadd(const Sender; Value: integer);
begin
AddToCSV(UInt32ToUtf8(Value),fAdd);
end;
procedure TTestLowLevelCommon.intdel(const Sender; Value: integer);
begin
AddToCSV(UInt32ToUtf8(Value),fDel);
end;
procedure TTestLowLevelCommon.Integers;
procedure changes(const old,new,added,deleted: RawUTF8);
var o,n: TIntegerDynArray;
begin
CSVToIntegerDynArray(Pointer(old),o);
CSVToIntegerDynArray(Pointer(new),n);
fAdd := '';
fDel := '';
NotifySortedIntegerChanges(pointer(o),pointer(n),length(o),length(n),intadd,intdel,self);
Check(fAdd = added, 'added');
Check(fDel = deleted, 'deleted');
end;
var i32: TIntegerDynArray;
i64: TInt64DynArray;
i,n: integer;
begin
check(i32=nil);
DeduplicateInteger(i32);
check(i32=nil);
SetLength(i32,2);
i32[0] := 1;
QuickSortInteger(i32);
check(i32[0]=0);
check(i32[1]=1);
DeduplicateInteger(i32);
check(length(i32)=2);
check(i32[0]=0);
check(i32[1]=1);
i32[0] := 1;
DeduplicateInteger(i32);
check(length(i32)=1);
check(i32[0]=1);
SetLength(i32,6);
i32[4] := 1;
i32[5] := 2;
DeduplicateInteger(i32); // (1, 0, 0, 0, 1, 2)
check(length(i32)=3);
check(i32[0]=0);
check(i32[1]=1);
check(i32[2]=2);
SetLength(i32,6);
i32[4] := 3;
i32[5] := 3;
DeduplicateInteger(i32); // (0, 1, 2, 0, 3, 3)
check(length(i32)=4);
check(i32[0]=0);
check(i32[1]=1);
check(i32[2]=2);
check(i32[3]=3);
for n := 1 to 1000 do begin
SetLength(i32,n);
for i := 0 to n - 1 do
i32[i] := i and 15;
DeduplicateInteger(i32);
if n < 16 then
check(Length(i32) = n) else
check(Length(i32) = 16);
for i := 0 to high(i32) do
check(i32[i] = i);
end;
changes('','','','');
changes('1','1','','');
changes('','1','1','');
changes('1','','','1');
changes('1,2','1,3','3','2');
changes('2','1,3','1,3','2');
changes('','1,3','1,3','');
changes('1,2,3,4','1,2,3,4','','');
changes('1,2,3,4','1,2,3,4,5','5','');
changes('1,2,3,4','1,3,4','','2');
changes('1,2,3,4','3,4','','1,2');
changes('1,2,3,4','1,4','','2,3');
changes('1,2,3,4','','','1,2,3,4');
changes('1,2,3,4','5,6','5,6','1,2,3,4');
changes('1,2,4','1,3,5,6','3,5,6','2,4');
changes('1,2,4','3,5,6','3,5,6','1,2,4');
check(i64=nil);
DeduplicateInt64(i64);
check(i64=nil);
SetLength(i64,2);
i64[0] := 1;
QuickSortInt64(pointer(i64),0,1);
check(i64[0]=0);
check(i64[1]=1);
DeduplicateInt64(i64);
check(length(i64)=2);
check(i64[0]=0);
check(i64[1]=1);
i64[0] := 1;
DeduplicateInt64(i64);
check(length(i64)=1);
check(i64[0]=1);
SetLength(i64,6);
i64[4] := 1;
i64[5] := 2;
DeduplicateInt64(i64); // (1, 0, 0, 0, 1, 2)
check(length(i64)=3);
check(i64[0]=0);
check(i64[1]=1);
check(i64[2]=2);
SetLength(i64,6);
i64[4] := 3;
i64[5] := 3;
DeduplicateInt64(i64); // (0, 1, 2, 0, 3, 3)
check(length(i64)=4);
check(i64[0]=0);
check(i64[1]=1);
check(i64[2]=2);
check(i64[3]=3);
for n := 1 to 1000 do begin
SetLength(i64,n);
for i := 0 to n - 1 do
i64[i] := i and 15;
DeduplicateInt64(i64);
if n < 16 then
check(Length(i64) = n) else
check(Length(i64) = 16);
for i := 0 to high(i64) do
check(i64[i] = i);
end;
end;
procedure TTestLowLevelCommon.NumericalConversions;
var i, j, b, err: integer;
juint: cardinal absolute j;
k,l: Int64;
s,s2: RawUTF8;
d,e: double;
{$ifndef DELPHI5OROLDER}
c: currency;
ident: TRawUTF8DynArray;
{$endif}
a: shortstring;
u: string;
varint: array[0..255] of byte;
st: TFastReader;
PB,PC: PByte;
P: PUTF8Char;
crc, n: cardinal;
Timer: TPrecisionTimer;
begin
Check(Plural('row',0)='0 row');
Check(Plural('row',1)='1 row');
Check(Plural('row',2)='2 rows');
Check(Plural('row',20)='20 rows');
Check(Plural('row',200000)='200000 rows');
Check(not SameValue(386.0, 386.1));
Check(not SameValue(386.0, 700, 2));
Check(IntToThousandString(0)='0');
Check(IntToThousandString(1)='1');
Check(IntToThousandString(10)='10');
Check(IntToThousandString(100)='100');
Check(IntToThousandString(1000)='1,000');
Check(IntToThousandString(10000)='10,000');
Check(IntToThousandString(100000)='100,000');
Check(IntToThousandString(1000000)='1,000,000');
Check(IntToThousandString(-1)='-1');
Check(IntToThousandString(-10)='-10');
Check(IntToThousandString(-100)='-100');
Check(IntToThousandString(-1000)='-1,000');
Check(IntToThousandString(-10000)='-10,000');
Check(IntToThousandString(-100000)='-100,000');
Check(IntToThousandString(-1000000)='-1,000,000');
Check(UInt3DigitsToUTF8(1)='001');
Check(UInt3DigitsToUTF8(12)='012');
Check(UInt3DigitsToUTF8(123)='123');
Check(UInt4DigitsToUTF8(1)='0001');
Check(UInt4DigitsToUTF8(12)='0012');
Check(UInt4DigitsToUTF8(123)='0123');
Check(UInt4DigitsToUTF8(1234)='1234');
Check(MicroSecToString(0)='0us');
Check(MicroSecToString(QWord(-10))='0us');
Check(MicroSecToString(10)='10us');
Check(MicroSecToString(999)='999us');
Check(MicroSecToString(1000)='1ms');
Check(MicroSecToString(1001)='1ms');
Check(MicroSecToString(1010)='1.01ms');
Check(MicroSecToString(1100)='1.10ms');
Check(MicroSecToString(999999)='999.99ms');
Check(MicroSecToString(1000000)='1s');
Check(MicroSecToString(1000001)='1s');
Check(MicroSecToString(2030001)='2.03s');
Check(MicroSecToString(200000070001)='55h33');
Check(KB(-123)='-123 B');
Check(KB(0)='0 B');
Check(KB(123)='123 B');
Check(KB(1023)='1 KB');
Check(KB(1024)='1 KB');
Check(KB(1025)='1 KB');
Check(KB(16383)='16 KB');
Check(KB(16384)='16 KB');
Check(KB(16385)='16 KB');
Check(KB(