5
* Copyright (C) 2009 10gen Inc.
7
* This program is free software: you can redistribute it and/or modify
8
* it under the terms of the GNU Affero General Public License, version 3,
9
* as published by the Free Software Foundation.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU Affero General Public License for more details.
16
* You should have received a copy of the GNU Affero General Public License
17
* along with this program. If not, see <http://www.gnu.org/licenses/>.
22
#include "mongo/db/instance.h"
23
#include "mongo/scripting/engine.h"
24
#include "mongo/util/timer.h"
25
#include "mongo/dbtests/dbtests.h"
26
#include "mongo/db/json.h"
29
bool dbEval(const string& dbName , BSONObj& cmd, BSONObjBuilder& result, string& errmsg);
37
// Run any tests included with the scripting engine
38
globalScriptEngine->runTest();
46
s.reset( globalScriptEngine->newScope() );
48
s->setNumber( "x" , 5 );
49
ASSERT( 5 == s->getNumber( "x" ) );
51
s->setNumber( "x" , 1.67 );
52
ASSERT( 1.67 == s->getNumber( "x" ) );
54
s->setString( "s" , "eliot was here" );
55
ASSERT( "eliot was here" == s->getString( "s" ) );
57
s->setBoolean( "b" , true );
58
ASSERT( s->getBoolean( "b" ) );
60
s->setBoolean( "b" , false );
61
ASSERT( ! s->getBoolean( "b" ) );
68
/* Currently reset does not clear data in v8 or spidermonkey scopes. See SECURITY-10
70
s.reset( globalScriptEngine->newScope() );
72
s->setBoolean( "x" , true );
73
ASSERT( s->getBoolean( "x" ) );
76
ASSERT( !s->getBoolean( "x" ) );
84
// Test falsy javascript values
86
s.reset( globalScriptEngine->newScope() );
88
ASSERT( ! s->getBoolean( "notSet" ) );
90
s->setString( "emptyString" , "" );
91
ASSERT( ! s->getBoolean( "emptyString" ) );
93
s->setNumber( "notANumberVal" , std::numeric_limits<double>::quiet_NaN());
94
ASSERT( ! s->getBoolean( "notANumberVal" ) );
96
s->setElement( "nullVal" , BSONObjBuilder().appendNull("null").obj().getField("null") );
97
ASSERT( ! s->getBoolean( "nullVal" ) );
99
s->setNumber( "zeroVal" , 0 );
100
ASSERT( ! s->getBoolean( "zeroVal" ) );
104
class SimpleFunctions {
107
Scope * s = globalScriptEngine->newScope();
109
s->invoke( "x=5;" , 0, 0 );
110
ASSERT( 5 == s->getNumber( "x" ) );
112
s->invoke( "return 17;" , 0, 0 );
113
ASSERT( 17 == s->getNumber( "__returnValue" ) );
115
s->invoke( "function(){ return 17; }" , 0, 0 );
116
ASSERT( 17 == s->getNumber( "__returnValue" ) );
118
s->setNumber( "x" , 1.76 );
119
s->invoke( "return x == 1.76; " , 0, 0 );
120
ASSERT( s->getBoolean( "__returnValue" ) );
122
s->setNumber( "x" , 1.76 );
123
s->invoke( "return x == 1.79; " , 0, 0 );
124
ASSERT( ! s->getBoolean( "__returnValue" ) );
126
BSONObj obj = BSON( "" << 11.0 );
127
s->invoke( "function( z ){ return 5 + z; }" , &obj, 0 );
128
ASSERT_EQUALS( 16 , s->getNumber( "__returnValue" ) );
134
/** Installs a tee for auditing log messages, including those logged with tlog(). */
135
class LogRecordingScope {
137
LogRecordingScope() :
138
_oldTLogLevel( tlogLevel ) {
140
Logstream::get().addGlobalTee( &_tee );
142
~LogRecordingScope() {
143
Logstream::get().removeGlobalTee( &_tee );
144
tlogLevel = _oldTLogLevel;
146
/** @return most recent log entry. */
147
bool logged() const { return _tee.logged(); }
149
class Tee : public mongo::Tee {
154
virtual void write( LogLevel level, const string &str ) { _logged = true; }
155
bool logged() const { return _logged; }
163
/** Error logging in Scope::exec(). */
167
Scope *scope = globalScriptEngine->newScope();
169
// No error is logged when reportError == false.
170
ASSERT( !scope->exec( "notAFunction()", "foo", false, false, false ) );
171
ASSERT( !_logger.logged() );
173
// No error is logged for a valid statement.
174
ASSERT( scope->exec( "validStatement = true", "foo", false, true, false ) );
175
ASSERT( !_logger.logged() );
177
// An error is logged for an invalid statement when reportError == true.
178
ASSERT( !scope->exec( "notAFunction()", "foo", false, true, false ) );
179
ASSERT( _logger.logged() );
182
LogRecordingScope _logger;
185
/** Error logging in Scope::invoke(). */
186
class InvokeLogError {
189
Scope *scope = globalScriptEngine->newScope();
191
// No error is logged for a valid statement.
192
ASSERT_EQUALS( 0, scope->invoke( "validStatement = true", 0, 0 ) );
193
ASSERT( !_logger.logged() );
195
// An error is logged for an invalid statement.
197
scope->invoke( "notAFunction()", 0, 0 );
199
catch(const DBException&) {
200
// ignore the exception; just test that we logged something
202
ASSERT( _logger.logged() );
205
LogRecordingScope _logger;
208
class ObjectMapping {
211
Scope * s = globalScriptEngine->newScope();
213
BSONObj o = BSON( "x" << 17.0 << "y" << "eliot" << "z" << "sara" );
214
s->setObject( "blah" , o );
216
s->invoke( "return blah.x;" , 0, 0 );
217
ASSERT_EQUALS( 17 , s->getNumber( "__returnValue" ) );
218
s->invoke( "return blah.y;" , 0, 0 );
219
ASSERT_EQUALS( "eliot" , s->getString( "__returnValue" ) );
221
s->invoke( "return this.z;" , 0, &o );
222
ASSERT_EQUALS( "sara" , s->getString( "__returnValue" ) );
224
s->invoke( "return this.z == 'sara';" , 0, &o );
225
ASSERT_EQUALS( true , s->getBoolean( "__returnValue" ) );
227
s->invoke( "this.z == 'sara';" , 0, &o );
228
ASSERT_EQUALS( true , s->getBoolean( "__returnValue" ) );
230
s->invoke( "this.z == 'asara';" , 0, &o );
231
ASSERT_EQUALS( false , s->getBoolean( "__returnValue" ) );
233
s->invoke( "return this.x == 17;" , 0, &o );
234
ASSERT_EQUALS( true , s->getBoolean( "__returnValue" ) );
236
s->invoke( "return this.x == 18;" , 0, &o );
237
ASSERT_EQUALS( false , s->getBoolean( "__returnValue" ) );
239
s->invoke( "function(){ return this.x == 17; }" , 0, &o );
240
ASSERT_EQUALS( true , s->getBoolean( "__returnValue" ) );
242
s->invoke( "function(){ return this.x == 18; }" , 0, &o );
243
ASSERT_EQUALS( false , s->getBoolean( "__returnValue" ) );
245
s->invoke( "function (){ return this.x == 17; }" , 0, &o );
246
ASSERT_EQUALS( true , s->getBoolean( "__returnValue" ) );
248
s->invoke( "function z(){ return this.x == 18; }" , 0, &o );
249
ASSERT_EQUALS( false , s->getBoolean( "__returnValue" ) );
251
s->invoke( "function (){ this.x == 17; }" , 0, &o );
252
ASSERT_EQUALS( false , s->getBoolean( "__returnValue" ) );
254
s->invoke( "function z(){ this.x == 18; }" , 0, &o );
255
ASSERT_EQUALS( false , s->getBoolean( "__returnValue" ) );
257
s->invoke( "x = 5; for( ; x <10; x++){ a = 1; }" , 0, &o );
258
ASSERT_EQUALS( 10 , s->getNumber( "x" ) );
264
class ObjectDecoding {
267
Scope * s = globalScriptEngine->newScope();
269
s->invoke( "z = { num : 1 };" , 0, 0 );
270
BSONObj out = s->getObject( "z" );
271
ASSERT_EQUALS( 1 , out["num"].number() );
272
ASSERT_EQUALS( 1 , out.nFields() );
274
s->invoke( "z = { x : 'eliot' };" , 0, 0 );
275
out = s->getObject( "z" );
276
ASSERT_EQUALS( (string)"eliot" , out["x"].valuestr() );
277
ASSERT_EQUALS( 1 , out.nFields() );
279
BSONObj o = BSON( "x" << 17 );
280
s->setObject( "blah" , o );
281
out = s->getObject( "blah" );
282
ASSERT_EQUALS( 17 , out["x"].number() );
292
Scope * s = globalScriptEngine->newScope();
294
s->localConnect( "blah" );
296
s->invoke( "z = { _id : new ObjectId() , a : 123 };" , 0, 0 );
297
BSONObj out = s->getObject( "z" );
298
ASSERT_EQUALS( 123 , out["a"].number() );
299
ASSERT_EQUALS( jstOID , out["_id"].type() );
301
OID save = out["_id"].__oid();
303
s->setObject( "a" , out );
305
s->invoke( "y = { _id : a._id , a : 124 };" , 0, 0 );
306
out = s->getObject( "y" );
307
ASSERT_EQUALS( 124 , out["a"].number() );
308
ASSERT_EQUALS( jstOID , out["_id"].type() );
309
ASSERT_EQUALS( out["_id"].__oid().str() , save.str() );
311
s->invoke( "y = { _id : new ObjectId( a._id ) , a : 125 };" , 0, 0 );
312
out = s->getObject( "y" );
313
ASSERT_EQUALS( 125 , out["a"].number() );
314
ASSERT_EQUALS( jstOID , out["_id"].type() );
315
ASSERT_EQUALS( out["_id"].__oid().str() , save.str() );
325
Scope *s = globalScriptEngine->newScope();
327
BSONObj o = BSON( "foo" << "bar" );
328
s->setObject( "a.b", o );
329
ASSERT( s->getObject( "a" ).isEmpty() );
331
BSONObj o2 = BSONObj();
332
s->setObject( "a", o2 );
333
s->setObject( "a.b", o );
334
ASSERT( s->getObject( "a" ).isEmpty() );
336
o2 = fromjson( "{b:{}}" );
337
s->setObject( "a", o2 );
338
s->setObject( "a.b", o );
339
ASSERT( !s->getObject( "a" ).isEmpty() );
343
class ObjectModReadonlyTests {
346
Scope * s = globalScriptEngine->newScope();
348
BSONObj o = BSON( "x" << 17 << "y" << "eliot" << "z" << "sara" << "zz" << BSONObj() );
349
s->setObject( "blah" , o , true );
351
s->invoke( "blah.y = 'e'", 0, 0 );
352
BSONObj out = s->getObject( "blah" );
353
ASSERT( strlen( out["y"].valuestr() ) > 1 );
355
s->invoke( "blah.a = 19;" , 0, 0 );
356
out = s->getObject( "blah" );
357
ASSERT( out["a"].eoo() );
359
s->invoke( "blah.zz.a = 19;" , 0, 0 );
360
out = s->getObject( "blah" );
361
ASSERT( out["zz"].embeddedObject()["a"].eoo() );
363
s->setObject( "blah.zz", BSON( "a" << 19 ) );
364
out = s->getObject( "blah" );
365
ASSERT( out["zz"].embeddedObject()["a"].eoo() );
367
s->invoke( "delete blah['x']" , 0, 0 );
368
out = s->getObject( "blah" );
369
ASSERT( !out["x"].eoo() );
371
// read-only object itself can be overwritten
372
s->invoke( "blah = {}", 0, 0 );
373
out = s->getObject( "blah" );
374
ASSERT( out.isEmpty() );
376
// test array - can't implement this in v8
377
// o = fromjson( "{a:[1,2,3]}" );
378
// s->setObject( "blah", o, true );
379
// out = s->getObject( "blah" );
380
// s->invoke( "blah.a[ 0 ] = 4;", BSONObj() );
381
// s->invoke( "delete blah['a'][ 2 ];", BSONObj() );
382
// out = s->getObject( "blah" );
383
// ASSERT_EQUALS( 1.0, out[ "a" ].embeddedObject()[ 0 ].number() );
384
// ASSERT_EQUALS( 3.0, out[ "a" ].embeddedObject()[ 2 ].number() );
393
Scope * s = globalScriptEngine->newScope();
400
b.appendDate( "d" , 123456789 );
403
s->setObject( "x" , o );
405
s->invoke( "return x.d.getTime() != 12;" , 0, 0 );
406
ASSERT_EQUALS( true, s->getBoolean( "__returnValue" ) );
408
s->invoke( "z = x.d.getTime();" , 0, 0 );
409
ASSERT_EQUALS( 123456789 , s->getNumber( "z" ) );
411
s->invoke( "z = { z : x.d }" , 0, 0 );
412
BSONObj out = s->getObject( "z" );
413
ASSERT( out["z"].type() == Date );
421
b.appendRegex( "r" , "^a" , "i" );
424
s->setObject( "x" , o );
426
s->invoke( "z = x.r.test( 'b' );" , 0, 0 );
427
ASSERT_EQUALS( false , s->getBoolean( "z" ) );
429
s->invoke( "z = x.r.test( 'a' );" , 0, 0 );
430
ASSERT_EQUALS( true , s->getBoolean( "z" ) );
432
s->invoke( "z = x.r.test( 'ba' );" , 0, 0 );
433
ASSERT_EQUALS( false , s->getBoolean( "z" ) );
435
s->invoke( "z = { a : x.r };" , 0, 0 );
437
BSONObj out = s->getObject("z");
438
ASSERT_EQUALS( (string)"^a" , out["a"].regex() );
439
ASSERT_EQUALS( (string)"i" , out["a"].regexFlags() );
441
// This regex used to cause a segfault because x isn't a valid flag for a js RegExp.
442
// Now it throws a JS exception.
443
BSONObj invalidRegex = BSON_ARRAY(BSON("regex" << BSONRegEx("asdf", "x")));
444
const char* code = "function (obj) {"
445
" var threw = false;"
447
" obj.regex;" // should throw
453
ASSERT_EQUALS(s->invoke(code, &invalidRegex, NULL), 0);
458
BSONObj o = fromjson( "{r:[1,2,3]}" );
459
s->setObject( "x", o, false );
460
BSONObj out = s->getObject( "x" );
461
ASSERT_EQUALS( Array, out.firstElement().type() );
463
s->setObject( "x", o, true );
464
out = s->getObject( "x" );
465
ASSERT_EQUALS( Array, out.firstElement().type() );
470
// test mutable object with symbol type
471
BSONObjBuilder builder;
472
builder.appendSymbol("sym", "value");
473
BSONObj in = builder.done();
474
s->setObject( "x", in, false );
475
BSONObj out = s->getObject( "x" );
476
ASSERT_EQUALS( Symbol, out.firstElement().type() );
479
s->setObject( "x", in, true );
480
out = s->getObject( "x" );
481
ASSERT_EQUALS( Symbol, out.firstElement().type() );
488
class SpecialDBTypes {
491
Scope * s = globalScriptEngine->newScope();
494
b.appendTimestamp( "a" , 123456789 );
495
b.appendMinKey( "b" );
496
b.appendMaxKey( "c" );
497
b.appendTimestamp( "d" , 1234000 , 9876 );
501
BSONObj t = b.done();
502
ASSERT_EQUALS( 1234000U , t["d"].timestampTime() );
503
ASSERT_EQUALS( 9876U , t["d"].timestampInc() );
506
s->setObject( "z" , b.obj() );
508
ASSERT( s->invoke( "y = { a : z.a , b : z.b , c : z.c , d: z.d }" , 0, 0 ) == 0 );
510
BSONObj out = s->getObject( "y" );
511
ASSERT_EQUALS( Timestamp , out["a"].type() );
512
ASSERT_EQUALS( MinKey , out["b"].type() );
513
ASSERT_EQUALS( MaxKey , out["c"].type() );
514
ASSERT_EQUALS( Timestamp , out["d"].type() );
516
ASSERT_EQUALS( 9876U , out["d"].timestampInc() );
517
ASSERT_EQUALS( 1234000U , out["d"].timestampTime() );
518
ASSERT_EQUALS( 123456789U , out["a"].date() );
524
class TypeConservation {
527
Scope * s = globalScriptEngine->newScope();
534
b.append( "a" , (int)5 );
535
b.append( "b" , 5.6 );
538
ASSERT_EQUALS( NumberInt , o["a"].type() );
539
ASSERT_EQUALS( NumberDouble , o["b"].type() );
541
s->setObject( "z" , o );
542
s->invoke( "return z" , 0, 0 );
543
BSONObj out = s->getObject( "__returnValue" );
544
ASSERT_EQUALS( 5 , out["a"].number() );
545
ASSERT_EQUALS( 5.6 , out["b"].number() );
547
ASSERT_EQUALS( NumberDouble , out["b"].type() );
548
ASSERT_EQUALS( NumberInt , out["a"].type() );
554
b.append( "a" , (int)5 );
555
b.append( "b" , 5.6 );
559
s->setObject( "z" , o , false );
560
s->invoke( "return z" , 0, 0 );
561
out = s->getObject( "__returnValue" );
562
ASSERT_EQUALS( 5 , out["a"].number() );
563
ASSERT_EQUALS( 5.6 , out["b"].number() );
565
ASSERT_EQUALS( NumberDouble , out["b"].type() );
566
ASSERT_EQUALS( NumberInt , out["a"].type() );
576
c.append( "0" , 5.5 );
578
b.appendArray( "a" , c.obj() );
584
ASSERT_EQUALS( NumberDouble , o["a"].embeddedObjectUserCheck()["0"].type() );
585
ASSERT_EQUALS( NumberInt , o["a"].embeddedObjectUserCheck()["1"].type() );
587
s->setObject( "z" , o , false );
588
out = s->getObject( "z" );
590
ASSERT_EQUALS( NumberDouble , out["a"].embeddedObjectUserCheck()["0"].type() );
591
ASSERT_EQUALS( NumberInt , out["a"].embeddedObjectUserCheck()["1"].type() );
593
s->invokeSafe( "z.z = 5;" , 0, 0 );
594
out = s->getObject( "z" );
595
ASSERT_EQUALS( 5 , out["z"].number() );
596
ASSERT_EQUALS( NumberDouble , out["a"].embeddedObjectUserCheck()["0"].type() );
597
// Commenting so that v8 tests will work
598
// ASSERT_EQUALS( NumberDouble , out["a"].embeddedObjectUserCheck()["1"].type() ); // TODO: this is technically bad, but here to make sure that i understand the behavior
601
// Eliot says I don't have to worry about this case
605
// o = fromjson( "{a:3.0,b:4.5}" );
606
// ASSERT_EQUALS( NumberDouble , o["a"].type() );
607
// ASSERT_EQUALS( NumberDouble , o["b"].type() );
609
// s->setObject( "z" , o , false );
610
// s->invoke( "return z" , BSONObj() );
611
// out = s->getObject( "__returnValue" );
612
// ASSERT_EQUALS( 3 , out["a"].number() );
613
// ASSERT_EQUALS( 4.5 , out["b"].number() );
615
// ASSERT_EQUALS( NumberDouble , out["b"].type() );
616
// ASSERT_EQUALS( NumberDouble , out["a"].type() );
627
auto_ptr<Scope> s( globalScriptEngine->newScope() );
628
s->localConnect( "blah" );
630
long long val = (long long)( 0xbabadeadbeefbaddULL );
631
b.append( "a", val );
632
BSONObj in = b.obj();
633
s->setObject( "a", in );
634
BSONObj out = s->getObject( "a" );
635
ASSERT_EQUALS( mongo::NumberLong, out.firstElement().type() );
637
ASSERT( s->exec( "b = {b:a.a}", "foo", false, true, false ) );
638
out = s->getObject( "b" );
639
ASSERT_EQUALS( mongo::NumberLong, out.firstElement().type() );
640
if( val != out.firstElement().numberLong() ) {
642
cout << out.firstElement().numberLong() << endl;
643
cout << out.toString() << endl;
644
ASSERT_EQUALS( val, out.firstElement().numberLong() );
647
ASSERT( s->exec( "c = {c:a.a.toString()}", "foo", false, true, false ) );
648
out = s->getObject( "c" );
650
ss << "NumberLong(\"" << val << "\")";
651
ASSERT_EQUALS( ss.str(), out.firstElement().valuestr() );
653
ASSERT( s->exec( "d = {d:a.a.toNumber()}", "foo", false, true, false ) );
654
out = s->getObject( "d" );
655
ASSERT_EQUALS( NumberDouble, out.firstElement().type() );
656
ASSERT_EQUALS( double( val ), out.firstElement().number() );
658
ASSERT( s->exec( "e = {e:a.a.floatApprox}", "foo", false, true, false ) );
659
out = s->getObject( "e" );
660
ASSERT_EQUALS( NumberDouble, out.firstElement().type() );
661
ASSERT_EQUALS( double( val ), out.firstElement().number() );
663
ASSERT( s->exec( "f = {f:a.a.top}", "foo", false, true, false ) );
664
out = s->getObject( "f" );
665
ASSERT( NumberDouble == out.firstElement().type() || NumberInt == out.firstElement().type() );
667
s->setObject( "z", BSON( "z" << (long long)( 4 ) ) );
668
ASSERT( s->exec( "y = {y:z.z.top}", "foo", false, true, false ) );
669
out = s->getObject( "y" );
670
ASSERT_EQUALS( Undefined, out.firstElement().type() );
672
ASSERT( s->exec( "x = {x:z.z.floatApprox}", "foo", false, true, false ) );
673
out = s->getObject( "x" );
674
ASSERT( NumberDouble == out.firstElement().type() || NumberInt == out.firstElement().type() );
675
ASSERT_EQUALS( double( 4 ), out.firstElement().number() );
677
ASSERT( s->exec( "w = {w:z.z}", "foo", false, true, false ) );
678
out = s->getObject( "w" );
679
ASSERT_EQUALS( mongo::NumberLong, out.firstElement().type() );
680
ASSERT_EQUALS( 4, out.firstElement().numberLong() );
688
auto_ptr<Scope> s( globalScriptEngine->newScope() );
689
s->localConnect( "blah" );
695
b.append( "b" , (long long)5 );
696
b.append( "c" , (long long)pow( 2.0, 29 ) );
697
b.append( "d" , (long long)pow( 2.0, 30 ) );
698
b.append( "e" , (long long)pow( 2.0, 31 ) );
699
b.append( "f" , (long long)pow( 2.0, 45 ) );
702
s->setObject( "a" , in );
704
ASSERT( s->exec( "x = tojson( a ); " ,"foo" , false , true , false ) );
705
string outString = s->getString( "x" );
707
ASSERT( s->exec( (string)"y = " + outString , "foo2" , false , true , false ) );
708
BSONObj out = s->getObject( "y" );
709
ASSERT_EQUALS( in , out );
713
class NumberLongUnderLimit {
716
auto_ptr<Scope> s( globalScriptEngine->newScope() );
717
s->localConnect( "blah" );
720
long long val = (long long)( 9007199254740991ULL );
721
b.append( "a", val );
722
BSONObj in = b.obj();
723
s->setObject( "a", in );
724
BSONObj out = s->getObject( "a" );
725
ASSERT_EQUALS( mongo::NumberLong, out.firstElement().type() );
727
ASSERT( s->exec( "b = {b:a.a}", "foo", false, true, false ) );
728
out = s->getObject( "b" );
729
ASSERT_EQUALS( mongo::NumberLong, out.firstElement().type() );
730
if( val != out.firstElement().numberLong() ) {
732
cout << out.firstElement().numberLong() << endl;
733
cout << out.toString() << endl;
734
ASSERT_EQUALS( val, out.firstElement().numberLong() );
737
ASSERT( s->exec( "c = {c:a.a.toString()}", "foo", false, true, false ) );
738
out = s->getObject( "c" );
740
ss << "NumberLong(\"" << val << "\")";
741
ASSERT_EQUALS( ss.str(), out.firstElement().valuestr() );
743
ASSERT( s->exec( "d = {d:a.a.toNumber()}", "foo", false, true, false ) );
744
out = s->getObject( "d" );
745
ASSERT_EQUALS( NumberDouble, out.firstElement().type() );
746
ASSERT_EQUALS( double( val ), out.firstElement().number() );
748
ASSERT( s->exec( "e = {e:a.a.floatApprox}", "foo", false, true, false ) );
749
out = s->getObject( "e" );
750
ASSERT_EQUALS( NumberDouble, out.firstElement().type() );
751
ASSERT_EQUALS( double( val ), out.firstElement().number() );
753
ASSERT( s->exec( "f = {f:a.a.top}", "foo", false, true, false ) );
754
out = s->getObject( "f" );
755
ASSERT( Undefined == out.firstElement().type() );
762
BSONObj build( int depth ) {
764
b.append( "0" , depth );
766
b.appendArray( "1" , build( depth - 1 ) );
771
Scope * s = globalScriptEngine->newScope();
773
s->localConnect( "blah" );
775
for ( int i=5; i<100 ; i += 10 ) {
776
s->setObject( "a" , build(i) , false );
777
s->invokeSafe( "tojson( a )" , 0, 0 );
779
s->setObject( "a" , build(5) , true );
780
s->invokeSafe( "tojson( a )" , 0, 0 );
788
* Test exec() timeout value terminates execution (SERVER-8053)
793
scoped_ptr<Scope> scope(globalScriptEngine->newScope());
794
scope->localConnect("ExecTimeoutDB");
795
// assert timeout occured
796
ASSERT(!scope->exec("var a = 1; while (true) { ; }",
797
"ExecTimeout", false, true, false, 1));
802
* Test exec() timeout value terminates execution (SERVER-8053)
804
class ExecNoTimeout {
807
scoped_ptr<Scope> scope(globalScriptEngine->newScope());
808
scope->localConnect("ExecNoTimeoutDB");
809
// assert no timeout occured
810
ASSERT(scope->exec("var a = function() { return 1; }",
811
"ExecNoTimeout", false, true, false, 5 * 60 * 1000));
816
* Test invoke() timeout value terminates execution (SERVER-8053)
818
class InvokeTimeout {
821
scoped_ptr<Scope> scope(globalScriptEngine->newScope());
822
scope->localConnect("InvokeTimeoutDB");
824
// scope timeout after 500ms
827
scope->invokeSafe("function() { "
831
} catch (const DBException&) {
839
* Test invoke() timeout value does not terminate execution (SERVER-8053)
841
class InvokeNoTimeout {
844
scoped_ptr<Scope> scope(globalScriptEngine->newScope());
845
scope->localConnect("InvokeTimeoutDB");
847
// invoke completes before timeout
848
scope->invokeSafe("function() { "
849
" for (var i=0; i<1; i++) { ; } "
851
0, 0, 5 * 60 * 1000);
856
void dummy_function_to_force_dbeval_cpp_linking() {
858
BSONObjBuilder result;
860
dbEval( "test", cmd, result, errmsg);
864
DBDirectClient client;
868
Utf8Check() { reset(); }
869
~Utf8Check() { reset(); }
871
if( !globalScriptEngine->utf8Ok() ) {
872
log() << "warning: utf8 not supported" << endl;
875
string utf8ObjSpec = "{'_id':'\\u0001\\u007f\\u07ff\\uffff'}";
876
BSONObj utf8Obj = fromjson( utf8ObjSpec );
877
client.insert( ns(), utf8Obj );
878
client.eval( "unittest", "v = db.jstests.utf8check.findOne(); db.jstests.utf8check.remove( {} ); db.jstests.utf8check.insert( v );" );
879
check( utf8Obj, client.findOne( ns(), BSONObj() ) );
882
void check( const BSONObj &one, const BSONObj &two ) {
883
if ( one.woCompare( two ) != 0 ) {
884
static string fail = string( "Assertion failure expected " ) + one.toString() + ", got " + two.toString();
885
FAIL( fail.c_str() );
889
client.dropCollection( ns() );
891
static const char *ns() { return "unittest.jstests.utf8check"; }
894
class LongUtf8String {
896
LongUtf8String() { reset(); }
897
~LongUtf8String() { reset(); }
899
if( !globalScriptEngine->utf8Ok() )
901
client.eval( "unittest", "db.jstests.longutf8string.save( {_id:'\\uffff\\uffff\\uffff\\uffff'} )" );
905
client.dropCollection( ns() );
907
static const char *ns() { return "unittest.jstests.longutf8string"; }
910
class InvalidUTF8Check {
913
if( !globalScriptEngine->utf8Ok() )
917
s.reset( globalScriptEngine->newScope() );
923
crap[0] = (char) 128;
925
crap[2] = (char) 128;
930
bb.append( "x" , crap );
934
//cout << "ELIOT: " << b.jsonString() << endl;
935
// its ok if this is handled by js, just can't create a c++ exception
936
s->invoke( "x=this.x.length;" , 0, &b );
943
Scope * s = globalScriptEngine->newScope();
948
b.appendCode( "b" , "function(){ out.b = 11; }" );
949
b.appendCodeWScope( "c" , "function(){ out.c = 12; }" , BSONObj() );
950
b.appendCodeWScope( "d" , "function(){ out.d = 13 + bleh; }" , BSON( "bleh" << 5 ) );
951
s->setObject( "foo" , b.obj() );
954
s->invokeSafe( "out = {}; out.a = foo.a; foo.b(); foo.c();" , 0, 0 );
955
BSONObj out = s->getObject( "out" );
957
ASSERT_EQUALS( 1 , out["a"].number() );
958
ASSERT_EQUALS( 11 , out["b"].number() );
959
ASSERT_EQUALS( 12 , out["c"].number() );
961
// Guess we don't care about this
962
//s->invokeSafe( "foo.d() " , BSONObj() );
963
//out = s->getObject( "out" );
964
//ASSERT_EQUALS( 18 , out["d"].number() );
971
namespace RoundTripTests {
973
// Inherit from this class to test round tripping of JSON objects
974
class TestRoundTrip {
976
virtual ~TestRoundTrip() {}
979
// Insert in Javascript -> Find using DBDirectClient
981
// Drop the collection
982
client.dropCollection( "unittest.testroundtrip" );
984
// Insert in Javascript
985
stringstream jsInsert;
986
jsInsert << "db.testroundtrip.insert(" << jsonIn() << ")";
987
ASSERT_TRUE( client.eval( "unittest" , jsInsert.str() ) );
989
// Find using DBDirectClient
990
BSONObj excludeIdProjection = BSON( "_id" << 0 );
991
BSONObj directFind = client.findOne( "unittest.testroundtrip",
993
&excludeIdProjection);
994
bsonEquals( bson(), directFind );
997
// Insert using DBDirectClient -> Find in Javascript
999
// Drop the collection
1000
client.dropCollection( "unittest.testroundtrip" );
1002
// Insert using DBDirectClient
1003
client.insert( "unittest.testroundtrip" , bson() );
1005
// Find in Javascript
1006
stringstream jsFind;
1007
jsFind << "dbref = db.testroundtrip.findOne( { } , { _id : 0 } )\n"
1008
<< "assert.eq(dbref, " << jsonOut() << ")";
1009
ASSERT_TRUE( client.eval( "unittest" , jsFind.str() ) );
1013
// Methods that must be defined by child classes
1014
virtual BSONObj bson() const = 0;
1015
virtual string json() const = 0;
1017
// This can be overriden if a different meaning of equality besides woCompare is needed
1018
virtual void bsonEquals( const BSONObj &expected, const BSONObj &actual ) {
1019
if ( expected.woCompare( actual ) ) {
1020
out() << "want:" << expected.jsonString() << " size: " << expected.objsize() << endl;
1021
out() << "got :" << actual.jsonString() << " size: " << actual.objsize() << endl;
1022
out() << expected.hexDump() << endl;
1023
out() << actual.hexDump() << endl;
1025
ASSERT( !expected.woCompare( actual ) );
1028
// This can be overriden if the JSON representation is altered on the round trip
1029
virtual string jsonIn() const {
1032
virtual string jsonOut() const {
1037
class DBRefTest : public TestRoundTrip {
1038
virtual BSONObj bson() const {
1041
memset( &o, 0, 12 );
1042
BSONObjBuilder subBuilder(b.subobjStart("a"));
1043
subBuilder.append("$ref", "ns");
1044
subBuilder.append("$id", o);
1048
virtual string json() const {
1049
return "{ \"a\" : DBRef( \"ns\", ObjectId( \"000000000000000000000000\" ) ) }";
1052
// A "fetch" function is added to the DBRef object when it is inserted using the
1053
// constructor, so we need to compare the fields individually
1054
virtual void bsonEquals( const BSONObj &expected, const BSONObj &actual ) {
1055
ASSERT_EQUALS( expected["a"].type() , actual["a"].type() );
1056
ASSERT_EQUALS( expected["a"]["$id"].OID() , actual["a"]["$id"].OID() );
1057
ASSERT_EQUALS( expected["a"]["$ref"].String() , actual["a"]["$ref"].String() );
1061
class DBPointerTest : public TestRoundTrip {
1062
virtual BSONObj bson() const {
1065
memset( &o, 0, 12 );
1066
b.appendDBRef( "a" , "ns" , o );
1069
virtual string json() const {
1070
return "{ \"a\" : DBPointer( \"ns\", ObjectId( \"000000000000000000000000\" ) ) }";
1074
class InformalDBRefTest : public TestRoundTrip {
1075
virtual BSONObj bson() const {
1077
BSONObjBuilder subBuilder(b.subobjStart("a"));
1078
subBuilder.append("$ref", "ns");
1079
subBuilder.append("$id", "000000000000000000000000");
1084
// Don't need to return anything because we are overriding both jsonOut and jsonIn
1085
virtual string json() const { return ""; }
1087
// Need to override these because the JSON doesn't actually round trip.
1088
// An object with "$ref" and "$id" fields is handled specially and different on the way out.
1089
virtual string jsonOut() const {
1090
return "{ \"a\" : DBRef( \"ns\", \"000000000000000000000000\" ) }";
1092
virtual string jsonIn() const {
1094
ss << "{ \"a\" : { \"$ref\" : \"ns\" , " <<
1095
"\"$id\" : \"000000000000000000000000\" } }";
1100
class InformalDBRefOIDTest : public TestRoundTrip {
1101
virtual BSONObj bson() const {
1104
memset( &o, 0, 12 );
1105
BSONObjBuilder subBuilder(b.subobjStart("a"));
1106
subBuilder.append("$ref", "ns");
1107
subBuilder.append("$id", o);
1112
// Don't need to return anything because we are overriding both jsonOut and jsonIn
1113
virtual string json() const { return ""; }
1115
// Need to override these because the JSON doesn't actually round trip.
1116
// An object with "$ref" and "$id" fields is handled specially and different on the way out.
1117
virtual string jsonOut() const {
1118
return "{ \"a\" : DBRef( \"ns\", ObjectId( \"000000000000000000000000\" ) ) }";
1120
virtual string jsonIn() const {
1122
ss << "{ \"a\" : { \"$ref\" : \"ns\" , " <<
1123
"\"$id\" : ObjectId( \"000000000000000000000000\" ) } }";
1128
class InformalDBRefExtraFieldTest : public TestRoundTrip {
1129
virtual BSONObj bson() const {
1132
memset( &o, 0, 12 );
1133
BSONObjBuilder subBuilder(b.subobjStart("a"));
1134
subBuilder.append("$ref", "ns");
1135
subBuilder.append("$id", o);
1136
subBuilder.append("otherfield", "value");
1141
// Don't need to return anything because we are overriding both jsonOut and jsonIn
1142
virtual string json() const { return ""; }
1144
// Need to override these because the JSON doesn't actually round trip.
1145
// An object with "$ref" and "$id" fields is handled specially and different on the way out.
1146
virtual string jsonOut() const {
1147
return "{ \"a\" : DBRef( \"ns\", ObjectId( \"000000000000000000000000\" ) ) }";
1149
virtual string jsonIn() const {
1151
ss << "{ \"a\" : { \"$ref\" : \"ns\" , " <<
1152
"\"$id\" : ObjectId( \"000000000000000000000000\" ) , " <<
1153
"\"otherfield\" : \"value\" } }";
1158
} // namespace RoundTripTests
1163
void pp( const char * s , BSONElement e ) {
1165
const char * data = e.binData( len );
1166
cout << s << ":" << e.binDataType() << "\t" << len << endl;
1168
for ( int i=0; i<len; i++ )
1169
cout << (int)(data[i]) << " ";
1174
Scope * s = globalScriptEngine->newScope();
1175
s->localConnect( "asd" );
1176
const char * foo = "asdas\0asdasd";
1177
const char * base64 = "YXNkYXMAYXNkYXNk";
1182
b.append( "a" , 7 );
1183
b.appendBinData( "b" , 12 , BinDataGeneral , foo );
1185
s->setObject( "x" , in );
1188
s->invokeSafe( "myb = x.b; print( myb ); printjson( myb );" , 0, 0 );
1189
s->invokeSafe( "y = { c : myb };" , 0, 0 );
1191
BSONObj out = s->getObject( "y" );
1192
ASSERT_EQUALS( BinData , out["c"].type() );
1193
// pp( "in " , in["b"] );
1194
// pp( "out" , out["c"] );
1195
ASSERT_EQUALS( 0 , in["b"].woCompare( out["c"] , false ) );
1197
// check that BinData js class is utilized
1198
s->invokeSafe( "q = x.b.toString();", 0, 0 );
1199
stringstream expected;
1200
expected << "BinData(" << BinDataGeneral << ",\"" << base64 << "\")";
1201
ASSERT_EQUALS( expected.str(), s->getString( "q" ) );
1203
stringstream scriptBuilder;
1204
scriptBuilder << "z = { c : new BinData( " << BinDataGeneral << ", \"" << base64 << "\" ) };";
1205
string script = scriptBuilder.str();
1206
s->invokeSafe( script.c_str(), 0, 0 );
1207
out = s->getObject( "z" );
1208
// pp( "out" , out["c"] );
1209
ASSERT_EQUALS( 0 , in["b"].woCompare( out["c"] , false ) );
1211
s->invokeSafe( "a = { f: new BinData( 128, \"\" ) };", 0, 0 );
1212
out = s->getObject( "a" );
1214
out[ "f" ].binData( len );
1215
ASSERT_EQUALS( 0, len );
1216
ASSERT_EQUALS( 128, out[ "f" ].binDataType() );
1225
Scope * s = globalScriptEngine->newScope();
1227
ASSERT( s->exec( "a = 5;" , "a" , false , true , false ) );
1228
ASSERT_EQUALS( 5 , s->getNumber("a" ) );
1230
ASSERT( s->exec( "var b = 6;" , "b" , false , true , false ) );
1231
ASSERT_EQUALS( 6 , s->getNumber("b" ) );
1239
BSONObj start = BSON( "x" << 5.0 );
1243
s.reset( globalScriptEngine->newScope() );
1245
ScriptingFunction f = s->createFunction( "return this.x + 6;" );
1249
for ( ; n < 10000 ; n++ ) {
1250
s->invoke( f , &empty, &start );
1251
ASSERT_EQUALS( 11 , s->getNumber( "__returnValue" ) );
1253
//cout << "speed1: " << ( n / t.millis() ) << " ops/ms" << endl;
1261
s.reset( globalScriptEngine->newScope() );
1263
s->invokeSafe( "x = 5;" , 0, 0 );
1266
s->append( b , "z" , "x" );
1267
ASSERT_EQUALS( BSON( "z" << 5 ) , b.obj() );
1270
s->invokeSafe( "x = function(){ return 17; }" , 0, 0 );
1274
s->append( b , "z" , "x" );
1278
s->invokeSafe( "foo = this.z();" , 0, &temp );
1279
ASSERT_EQUALS( 17 , s->getNumber( "foo" ) );
1287
s.reset( globalScriptEngine->newScope() );
1289
s->setNumber( "x" , 5 );
1290
ASSERT_EQUALS( 5 , s->getNumber( "x" ) );
1291
ASSERT_EQUALS( Undefined , s->type( "y" ) );
1293
s->rename( "x" , "y" );
1294
ASSERT_EQUALS( 5 , s->getNumber( "y" ) );
1295
ASSERT_EQUALS( Undefined , s->type( "x" ) );
1297
s->rename( "y" , "x" );
1298
ASSERT_EQUALS( 5 , s->getNumber( "x" ) );
1299
ASSERT_EQUALS( Undefined , s->type( "y" ) );
1304
class InvalidStoredJS {
1307
BSONObjBuilder query;
1308
query.append( "_id" , "invalidstoredjs1" );
1310
BSONObjBuilder update;
1311
update.append( "_id" , "invalidstoredjs1" );
1312
update.appendCode( "value" , "function () { db.test.find().forEach(function(obj) { continue; }); }" );
1313
client.update( "test.system.js" , query.obj() , update.obj() , true /* upsert */ );
1315
scoped_ptr<Scope> s( globalScriptEngine->newScope() );
1316
client.eval( "test" , "invalidstoredjs1()" );
1320
ASSERT( client.eval( "test" , "return 5 + 12" , info , ret ) );
1321
ASSERT_EQUALS( 17 , ret.number() );
1325
class All : public Suite {
1327
All() : Suite( "js" ) {
1328
// Initialize the Javascript interpreter
1329
ScriptEngine::setup();
1333
add< BuiltinTests >();
1334
add< BasicScope >();
1335
add< ResetScope >();
1336
add< FalseTests >();
1337
add< SimpleFunctions >();
1338
add< ExecLogError >();
1339
add< InvokeLogError >();
1340
add< ExecTimeout >();
1341
add< ExecNoTimeout >();
1342
add< InvokeTimeout >();
1343
add< InvokeNoTimeout >();
1345
add< ObjectMapping >();
1346
add< ObjectDecoding >();
1347
add< JSOIDTests >();
1348
add< SetImplicit >();
1349
add< ObjectModReadonlyTests >();
1350
add< OtherJSTypes >();
1351
add< SpecialDBTypes >();
1352
add< TypeConservation >();
1353
add< NumberLong >();
1354
add< NumberLong2 >();
1355
add< RenameTest >();
1357
add< WeirdObjects >();
1359
add< BinDataType >();
1365
add< InvalidUTF8Check >();
1367
add< LongUtf8String >();
1370
add< InvalidStoredJS >();
1372
add< RoundTripTests::DBRefTest >();
1373
add< RoundTripTests::DBPointerTest >();
1374
add< RoundTripTests::InformalDBRefTest >();
1375
add< RoundTripTests::InformalDBRefOIDTest >();
1376
add< RoundTripTests::InformalDBRefExtraFieldTest >();
1380
} // namespace JavaJSTests