Add extra tests and fixes for ota.

FossilOrigin-Name: e0b7151962fedbcac975f2216fd6b33b995a8945
This commit is contained in:
dan 2015-02-16 21:13:19 +00:00
parent 5902352fca
commit f5fe23909d
6 changed files with 282 additions and 93 deletions

View File

@ -56,6 +56,26 @@ proc create_ota4 {filename} {
ota1 close
return $filename
}
#
# Create a simple OTA database. That expects to write to a table:
#
# CREATE TABLE t1(c, b, '(a)' INTEGER PRIMARY KEY);
#
# This OTA includes both insert and delete operations.
#
proc create_ota4b {filename} {
forcedelete $filename
sqlite3 ota1 $filename
ota1 eval {
CREATE TABLE data_t1(c, b, '(a)', ota_control);
INSERT INTO data_t1 VALUES(3, 2, 1, 0);
INSERT INTO data_t1 VALUES(5, NULL, 2, 1);
INSERT INTO data_t1 VALUES(9, 8, 3, 0);
INSERT INTO data_t1 VALUES(11, NULL, 4, 1);
}
ota1 close
return $filename
}
# Create a simple OTA database. That expects to write to a table:
#
@ -368,6 +388,44 @@ foreach {tn3 create_vfs destroy_vfs} {
}
}
foreach {tn2 cmd} {1 run_ota 2 step_ota} {
foreach {tn schema} {
1 {
CREATE TABLE t1(c, b, '(a)' INTEGER PRIMARY KEY);
CREATE INDEX i1 ON t1(c, b);
}
2 {
CREATE TABLE t1(c, b, '(a)' PRIMARY KEY);
}
3 {
CREATE TABLE t1(c, b, '(a)' PRIMARY KEY) WITHOUT ROWID;
}
} {
reset_db
execsql $schema
execsql {
INSERT INTO t1('(a)', b, c) VALUES(2, 'hello', 'world');
INSERT INTO t1('(a)', b, c) VALUES(4, 'hello', 'planet');
INSERT INTO t1('(a)', b, c) VALUES(6, 'hello', 'xyz');
}
do_test $tn3.4.$tn2.$tn.1 {
create_ota4b ota.db
$cmd test.db ota.db
} {SQLITE_DONE}
do_execsql_test $tn3.4.$tn2.$tn.2 {
SELECT * FROM t1 ORDER BY "(a)" ASC;
} {
3 2 1
9 8 3
xyz hello 6
}
do_execsql_test $tn3.4.$tn2.$tn.3 { PRAGMA integrity_check } ok
}
}
#-------------------------------------------------------------------------
#
foreach {tn2 cmd} {1 run_ota 2 step_ota} {
@ -435,6 +493,8 @@ foreach {tn3 create_vfs destroy_vfs} {
# * A no-PK table with no ota_rowid column.
# * A PK table with an ota_rowid column.
#
# 6: An update string of the wrong length
#
ifcapable fts3 {
foreach {tn schema error} {
1 {
@ -461,6 +521,12 @@ foreach {tn3 create_vfs destroy_vfs} {
CREATE TABLE t1(a, b PRIMARY KEY) WITHOUT ROWID;
CREATE TABLE ota.data_t1(a, b, ota_rowid, ota_control);
} {SQLITE_ERROR - table data_t1 may not have ota_rowid column}
6 {
CREATE TABLE t1(a, b PRIMARY KEY) WITHOUT ROWID;
CREATE TABLE ota.data_t1(a, b, ota_control);
INSERT INTO ota.data_t1 VALUES(1, 2, 'x.x');
} {SQLITE_ERROR - invalid ota_control value}
} {
reset_db

View File

@ -71,7 +71,6 @@ do_test 2.2 {
file exists test.db-wal
} {1}
breakpoint
do_test 2.3 {
sqlite3ota ota test.db ota.db
ota step
@ -81,5 +80,86 @@ do_test 2.4 {
list [catch {ota close} msg] $msg
} {1 {SQLITE_ERROR - cannot update wal mode database}}
#--------------------------------------------------------------------
# Test a constraint violation message with an unusual table name.
# Specifically, one for which the first character is a codepoint
# smaller than 30 (character '0').
#
reset_db
do_execsql_test 3.1 {
CREATE TABLE "(t1)"(a PRIMARY KEY, b, c);
INSERT INTO "(t1)" VALUES(1, 2, 3);
INSERT INTO "(t1)" VALUES(4, 5, 6);
}
db close
do_test 3.2 {
forcedelete ota.db
sqlite3 dbo ota.db
dbo eval {
CREATE TABLE "data_(t1)"(a, b, c, ota_control);
INSERT INTO "data_(t1)" VALUES(4, 8, 9, 0);
}
dbo close
sqlite3ota ota test.db ota.db
ota step
ota step
} {SQLITE_CONSTRAINT}
do_test 3.3 {
list [catch {ota close} msg] $msg
} {1 {SQLITE_CONSTRAINT - UNIQUE constraint failed: (t1).a}}
#--------------------------------------------------------------------
# Check that once an OTA update has been applied, attempting to apply
# it a second time is a no-op (as the state stored in the OTA database is
# "all steps completed").
#
reset_db
do_execsql_test 4.1 {
CREATE TABLE "(t1)"(a, b, c, PRIMARY KEY(c, b, a));
INSERT INTO "(t1)" VALUES(1, 2, 3);
INSERT INTO "(t1)" VALUES(4, 5, 6);
}
db close
do_test 4.2 {
forcedelete ota.db
sqlite3 dbo ota.db
dbo eval {
CREATE TABLE "data_(t1)"(a, b, c, ota_control);
INSERT INTO "data_(t1)" VALUES(7, 8, 9, 0);
INSERT INTO "data_(t1)" VALUES(1, 2, 3, 1);
}
dbo close
sqlite3ota ota test.db ota.db
while {[ota step]=="SQLITE_OK"} { }
ota close
} {SQLITE_DONE}
do_test 4.3 {
sqlite3ota ota test.db ota.db
ota step
} {SQLITE_DONE}
do_test 4.4 {
ota close
} {SQLITE_DONE}
# Also, check that an invalid state value in the ota_state table is
# detected and reported as corruption.
do_test 4.5 {
sqlite3 dbo ota.db
dbo eval { UPDATE ota_state SET v = -1 WHERE k = 1 }
dbo close
sqlite3ota ota test.db ota.db
ota step
} {SQLITE_CORRUPT}
do_test 4.6 {
list [catch {ota close} msg] $msg
} {1 SQLITE_CORRUPT}
finish_test

View File

@ -20,6 +20,7 @@ set ::testprefix otafault
do_test 1.1 {
forcedelete ota.db
execsql {
PRAGMA encoding = utf16;
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
CREATE INDEX t1cb ON t1(c, b);
INSERT INTO t1 VALUES(1, 1, 1);
@ -49,41 +50,69 @@ do_test 1.1 {
forcecopy ota.db ota.db.bak
} {}
do_faultsim_test 2 -faults oom-trans* -prep {
catch { db close }
forcedelete test.db-journal test.db-wal ota.db-journal ota.db-wal
forcecopy test.db.bak test.db
forcecopy ota.db.bak ota.db
} -body {
sqlite3ota ota test.db ota.db
while {[ota step]=="SQLITE_OK"} {}
ota close
} -test {
faultsim_test_result {0 SQLITE_DONE} \
{1 {SQLITE_NOMEM - out of memory}} \
{1 SQLITE_NOMEM} \
{1 SQLITE_IOERR_NOMEM} \
{1 {SQLITE_NOMEM - unable to open a temporary database file for storing temporary tables}}
if {$testrc==0} {
sqlite3 db test.db
faultsim_integrity_check
set res [db eval {
SELECT * FROM t1 UNION ALL SELECT * FROM t2;
}]
set expected [list {*}{
1 1 1 3 three 3 4 4 4
a a a c see c d d d
}]
sqlite3_shutdown
set lookaside_config [sqlite3_config_lookaside 0 0]
sqlite3_initialize
autoinstall_test_functions
if {$res != $expected} {
puts ""
puts "res: $res"
puts "exp: $expected"
error "data not as expected!"
foreach {tn f reslist} {
1 oom-tra* {
{0 SQLITE_DONE}
{1 {SQLITE_NOMEM - out of memory}}
{1 SQLITE_NOMEM}
{1 SQLITE_IOERR_NOMEM}
{1 {SQLITE_NOMEM - unable to open a temporary database file for storing temporary tables}}
}
2 ioerr-* {
{0 SQLITE_DONE}
{1 {SQLITE_IOERR - disk I/O error}}
{1 SQLITE_IOERR}
{1 SQLITE_IOERR_WRITE}
{1 SQLITE_IOERR_READ}
{1 SQLITE_IOERR_FSYNC}
{1 {SQLITE_ERROR - SQL logic error or missing database}}
{1 {SQLITE_ERROR - unable to open database: ota.db}}
{1 {SQLITE_IOERR - unable to open database: ota.db}}
}
} {
do_faultsim_test 2 -faults $::f -prep {
catch { db close }
forcedelete test.db-journal test.db-wal ota.db-journal ota.db-wal
forcecopy test.db.bak test.db
forcecopy ota.db.bak ota.db
} -body {
sqlite3ota ota test.db ota.db
while {[ota step]=="SQLITE_OK"} {}
ota close
} -test {
faultsim_test_result {*}$::reslist
if {$testrc==0} {
sqlite3 db test.db
faultsim_integrity_check
set res [db eval {
SELECT * FROM t1 UNION ALL SELECT * FROM t2;
}]
set expected [list {*}{
1 1 1 3 three 3 4 4 4
a a a c see c d d d
}]
if {$res != $expected} {
puts ""
puts "res: $res"
puts "exp: $expected"
error "data not as expected!"
}
}
}
}
catch {db close}
sqlite3_shutdown
sqlite3_config_lookaside {*}$lookaside_config
sqlite3_initialize
autoinstall_test_functions
proc copy_if_exists {src target} {
if {[file exists $src]} {
forcecopy $src $target

View File

@ -394,22 +394,25 @@ static int otaObjIterNext(sqlite3ota *p, OtaObjIter *pIter){
}else{
pIter->zTbl = (const char*)sqlite3_column_text(pIter->pTblIter, 0);
pIter->iTnum = sqlite3_column_int(pIter->pTblIter, 1);
rc = SQLITE_OK;
rc = pIter->zTbl ? SQLITE_OK : SQLITE_NOMEM;
}
}else{
if( pIter->zIdx==0 ){
sqlite3_bind_text(pIter->pIdxIter, 1, pIter->zTbl, -1, SQLITE_STATIC);
sqlite3_stmt *pIdx = pIter->pIdxIter;
rc = sqlite3_bind_text(pIdx, 1, pIter->zTbl, -1, SQLITE_STATIC);
}
rc = sqlite3_step(pIter->pIdxIter);
if( rc!=SQLITE_ROW ){
rc = sqlite3_reset(pIter->pIdxIter);
pIter->bCleanup = 1;
pIter->zIdx = 0;
}else{
pIter->zIdx = (const char*)sqlite3_column_text(pIter->pIdxIter, 0);
pIter->iTnum = sqlite3_column_int(pIter->pIdxIter, 1);
pIter->bUnique = sqlite3_column_int(pIter->pIdxIter, 2);
rc = SQLITE_OK;
if( rc==SQLITE_OK ){
rc = sqlite3_step(pIter->pIdxIter);
if( rc!=SQLITE_ROW ){
rc = sqlite3_reset(pIter->pIdxIter);
pIter->bCleanup = 1;
pIter->zIdx = 0;
}else{
pIter->zIdx = (const char*)sqlite3_column_text(pIter->pIdxIter, 0);
pIter->iTnum = sqlite3_column_int(pIter->pIdxIter, 1);
pIter->bUnique = sqlite3_column_int(pIter->pIdxIter, 2);
rc = pIter->zIdx ? SQLITE_OK : SQLITE_NOMEM;
}
}
}
}
@ -725,6 +728,7 @@ static int otaObjIterCacheTableInfo(sqlite3ota *p, OtaObjIter *pIter){
}
while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
if( zName==0 ) break; /* An OOM - finalize() below returns S_NOMEM */
for(i=iOrder; i<pIter->nTblCol; i++){
if( 0==strcmp(zName, pIter->azTblCol[i]) ) break;
}
@ -863,7 +867,7 @@ static char *otaObjIterGetIndexCols(
** its name. Otherwise, use "ota_rowid". */
if( pIter->eType==OTA_PK_IPK ){
int i;
for(i=0; i<pIter->nTblCol && pIter->abTblPk[i]==0; i++);
for(i=0; pIter->abTblPk[i]==0; i++);
assert( i<pIter->nTblCol );
zCol = pIter->azTblCol[i];
}else{
@ -1007,7 +1011,7 @@ static char *otaObjIterGetWhere(
*/
static void otaBadControlError(sqlite3ota *p){
p->rc = SQLITE_ERROR;
p->zErrmsg = sqlite3_mprintf("Invalid ota_control value");
p->zErrmsg = sqlite3_mprintf("invalid ota_control value");
}
@ -1086,15 +1090,19 @@ static char *otaWithoutRowidPK(sqlite3ota *p, OtaObjIter *pIter){
);
while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXList) ){
const char *zOrig = (const char*)sqlite3_column_text(pXList,3);
if( zOrig && strcmp(zOrig,"pk")==0 ){
p->rc = prepareFreeAndCollectError(p->db, &pXInfo, &p->zErrmsg,
sqlite3_mprintf("PRAGMA main.index_xinfo = %Q",
sqlite3_column_text(pXList,1))
);
if( zOrig && strcmp(zOrig, "pk")==0 ){
const char *zIdx = (const char*)sqlite3_column_text(pXList,1);
if( zIdx ){
p->rc = prepareFreeAndCollectError(p->db, &pXInfo, &p->zErrmsg,
sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", zIdx)
);
}
break;
}
}
sqlite3_finalize(pXList);
rc = sqlite3_finalize(pXList);
if( p->rc==SQLITE_OK ) p->rc = rc;
while( p->rc==SQLITE_OK && pXInfo && SQLITE_ROW==sqlite3_step(pXInfo) ){
if( sqlite3_column_int(pXInfo, 5) ){
/* int iCid = sqlite3_column_int(pXInfo, 0); */
@ -1105,7 +1113,6 @@ static char *otaWithoutRowidPK(sqlite3ota *p, OtaObjIter *pIter){
}
}
z = otaMPrintf(p, "%z)", z);
rc = sqlite3_finalize(pXInfo);
if( p->rc==SQLITE_OK ) p->rc = rc;
}
@ -1730,10 +1737,17 @@ static int otaStepType(sqlite3ota *p, const char **pzMask){
break;
}
case SQLITE_TEXT:
*pzMask = (const char*)sqlite3_column_text(p->objiter.pSelect, iCol);
case SQLITE_TEXT: {
const unsigned char *z = sqlite3_column_text(p->objiter.pSelect, iCol);
if( z==0 ){
p->rc = SQLITE_NOMEM;
}else{
*pzMask = (const char*)z;
}
res = OTA_UPDATE;
break;
}
default:
break;
@ -1813,7 +1827,9 @@ static int otaStep(sqlite3ota *p){
}
pVal = sqlite3_column_value(pIter->pSelect, i);
sqlite3_bind_value(pWriter, i+1, pVal);
p->rc = sqlite3_bind_value(pWriter, i+1, pVal);
if( p->rc==SQLITE_RANGE ) p->rc = SQLITE_OK;
if( p->rc ) goto step_out;
}
if( pIter->zIdx==0
&& (pIter->eType==OTA_PK_VTAB || pIter->eType==OTA_PK_NONE)
@ -1827,27 +1843,36 @@ static int otaStep(sqlite3ota *p){
*/
assertColumnName(pIter->pSelect, pIter->nCol+1, "ota_rowid");
pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1);
sqlite3_bind_value(pWriter, pIter->nCol+1, pVal);
p->rc = sqlite3_bind_value(pWriter, pIter->nCol+1, pVal);
}
if( p->rc==SQLITE_OK ){
sqlite3_step(pWriter);
p->rc = resetAndCollectError(pWriter, &p->zErrmsg);
}
sqlite3_step(pWriter);
p->rc = resetAndCollectError(pWriter, &p->zErrmsg);
}else if( eType==OTA_UPDATE ){
sqlite3_value *pVal;
sqlite3_stmt *pUpdate = 0;
otaGetUpdateStmt(p, pIter, zMask, &pUpdate);
if( pUpdate ){
for(i=0; p->rc==SQLITE_OK && i<pIter->nCol; i++){
char c = zMask[pIter->aiSrcOrder[i]];
pVal = sqlite3_column_value(pIter->pSelect, i);
sqlite3_bind_value(pUpdate, i+1, pVal);
if( pIter->abTblPk[i] || c=='x' || c=='d' ){
p->rc = sqlite3_bind_value(pUpdate, i+1, pVal);
}
}
if( pIter->eType==OTA_PK_VTAB || pIter->eType==OTA_PK_NONE ){
if( p->rc==SQLITE_OK
&& (pIter->eType==OTA_PK_VTAB || pIter->eType==OTA_PK_NONE)
){
/* Bind the ota_rowid value to column _rowid_ */
assertColumnName(pIter->pSelect, pIter->nCol+1, "ota_rowid");
pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1);
sqlite3_bind_value(pUpdate, pIter->nCol+1, pVal);
p->rc = sqlite3_bind_value(pUpdate, pIter->nCol+1, pVal);
}
if( p->rc==SQLITE_OK ){
sqlite3_step(pUpdate);
p->rc = resetAndCollectError(pUpdate, &p->zErrmsg);
}
sqlite3_step(pUpdate);
p->rc = resetAndCollectError(pUpdate, &p->zErrmsg);
}
}else{
/* no-op */
@ -2179,18 +2204,6 @@ sqlite3ota *sqlite3ota_open(const char *zTarget, const char *zOta){
p->rc = sqlite3_exec(p->db, OTA_CREATE_STATE, 0, 0, &p->zErrmsg);
}
/* Check that this is not a wal mode database. If it is, it cannot be
** updated. There is also a check for a live *-wal file in otaVfsAccess()
** function, on the off chance that the target is a wal database for
** which the first page of the db file has been overwritten by garbage
** during an earlier failed checkpoint. */
#if 0
if( p->rc==SQLITE_OK && p->pTargetFd->iWriteVer>1 ){
p->rc = SQLITE_ERROR;
p->zErrmsg = sqlite3_mprintf("cannot update wal mode database");
}
#endif
if( p->rc==SQLITE_OK ){
pState = otaLoadState(p);
assert( pState || p->rc!=SQLITE_OK );
@ -2209,6 +2222,9 @@ sqlite3ota *sqlite3ota_open(const char *zTarget, const char *zOta){
if( p->rc==SQLITE_OK ){
if( p->eStage==OTA_STAGE_OAL ){
/* Check that this is not a wal mode database. If it is, it cannot
** be updated. */
if( p->pTargetFd->pWalFd ){
p->rc = SQLITE_ERROR;
p->zErrmsg = sqlite3_mprintf("cannot update wal mode database");
@ -2243,6 +2259,8 @@ sqlite3ota *sqlite3ota_open(const char *zTarget, const char *zOta){
p->nStep = pState->nRow;
}else if( p->eStage==OTA_STAGE_DONE ){
p->rc = SQLITE_DONE;
}else{
p->rc = SQLITE_CORRUPT;
}
}
@ -2418,10 +2436,8 @@ static int otaVfsRead(
ota_file *p = (ota_file*)pFile;
int rc;
if( p->pOta
&& p->pOta->eStage==OTA_STAGE_CAPTURE
&& (p->openFlags & SQLITE_OPEN_WAL)
){
if( p->pOta && p->pOta->eStage==OTA_STAGE_CAPTURE ){
assert( p->openFlags & SQLITE_OPEN_WAL );
rc = otaCaptureWalRead(p->pOta, iOfst, iAmt);
}else{
rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst);
@ -2447,10 +2463,8 @@ static int otaVfsWrite(
){
ota_file *p = (ota_file*)pFile;
int rc;
if( p->pOta
&& p->pOta->eStage==OTA_STAGE_CAPTURE
&& (p->openFlags & SQLITE_OPEN_MAIN_DB)
){
if( p->pOta && p->pOta->eStage==OTA_STAGE_CAPTURE ){
assert( p->openFlags & SQLITE_OPEN_MAIN_DB );
rc = otaCaptureDbWrite(p->pOta, iOfst);
}else{
rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst);

View File

@ -1,5 +1,5 @@
C Add\sfurther\stests\sand\sfixes\sfor\sota.
D 2015-02-16T11:48:34.819
C Add\sextra\stests\sand\sfixes\sfor\sota.
D 2015-02-16T21:13:19.665
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in 6b9e7677829aa94b9f30949656e27312aefb9a46
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@ -125,9 +125,9 @@ F ext/misc/vtshim.c babb0dc2bf116029e3e7c9a618b8a1377045303e
F ext/misc/wholenumber.c 784b12543d60702ebdd47da936e278aa03076212
F ext/ota/README.txt 2ce4ffbb0aaa6731b041c27a7359f9a5f1c69152
F ext/ota/ota.c c11a85af71dccc45976622fe7a51169a481caa91
F ext/ota/ota1.test dee5b852353642a243e0bf414d332b1bccd5324f
F ext/ota/ota1.test 88a47987dc12780c23d9efbeb0e9416c838eb1f6
F ext/ota/ota10.test 85e0f6e7964db5007590c1b299e75211ed4240d4
F ext/ota/ota11.test 44e9ebdc1d9c23214c739f122f2b36e169e515aa
F ext/ota/ota11.test 0a0c56b9474f82097018a8f399172417737c64c9
F ext/ota/ota3.test cd654ef16fc6b3d3596894ee3f3b8fd821b969f5
F ext/ota/ota5.test ad0799daf8923ddebffe75ae8c5504ca90b7fadb
F ext/ota/ota6.test 40996b7716dee72a6c5d28c3bee436717a438d3d
@ -135,8 +135,8 @@ F ext/ota/ota7.test 1fe2c5761705374530e29f70c39693076028221a
F ext/ota/ota8.test cd70e63a0c29c45c0906692827deafa34638feda
F ext/ota/ota9.test d3eee95dd836824d07a22e5efcdb7bf6e869358b
F ext/ota/otaA.test ef4bfa8cfd4ed814ae86f7457b64aa2f18c90171
F ext/ota/otafault.test 508ba87c83d632670ac0f94371a465d4bb4d49dd
F ext/ota/sqlite3ota.c f2ea3fe9d7fceec5efc1ad62b8bf07ec607f99c5
F ext/ota/otafault.test fd3d4d9b9ed3cbe4461cde602e40f50825e0ee2a
F ext/ota/sqlite3ota.c f378ebb435ce79bb5060907c3bc994c5b48cbb85
F ext/ota/sqlite3ota.h 1cc7201086fe65a36957740381485a24738c4077
F ext/ota/test_ota.c 5dd58e4e6eb3ae7b471566616d44b701971bce88
F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
@ -1256,7 +1256,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
P f20779a6e890ba73bfaa904cefcf3a029b01fed4
R d9098c0831f1564055344cfb128a3e0f
P 62dc1fffc38cb157c15105098749b6dd0198eb84
R 26fc1fcf1b321dc2a4f4bbbaf0fa84e9
U dan
Z f0ac85428b81638540d3d3b58c8f458b
Z bb700675d8726f14df483f7f803a4d01

View File

@ -1 +1 @@
62dc1fffc38cb157c15105098749b6dd0198eb84
e0b7151962fedbcac975f2216fd6b33b995a8945