// jstests.cpp
//
/**
* Copyright (C) 2009-2014 MongoDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects
* for all of the code used other than as permitted herein. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you do not
* wish to do so, delete this exception statement from your version. If you
* delete this exception statement from all source files in the program,
* then also delete it in the license file.
*/
#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault
#include "mongo/platform/basic.h"
#include
#include
#include "mongo/base/parse_number.h"
#include "mongo/db/client.h"
#include "mongo/db/dbdirectclient.h"
#include "mongo/db/json.h"
#include "mongo/dbtests/dbtests.h"
#include "mongo/platform/decimal128.h"
#include "mongo/scripting/engine.h"
#include "mongo/util/concurrency/thread_name.h"
#include "mongo/util/log.h"
#include "mongo/util/timer.h"
using std::cout;
using std::endl;
using std::string;
using std::stringstream;
using std::unique_ptr;
using std::vector;
namespace JSTests {
class BuiltinTests {
public:
void run() {
// Run any tests included with the scripting engine
globalScriptEngine->runTest();
}
};
class BasicScope {
public:
void run() {
unique_ptr s;
s.reset(globalScriptEngine->newScope());
s->setNumber("x", 5);
ASSERT(5 == s->getNumber("x"));
s->setNumber("x", 1.67);
ASSERT(1.67 == s->getNumber("x"));
s->setString("s", "eliot was here");
ASSERT("eliot was here" == s->getString("s"));
s->setBoolean("b", true);
ASSERT(s->getBoolean("b"));
s->setBoolean("b", false);
ASSERT(!s->getBoolean("b"));
}
};
class ResetScope {
public:
void run() {
/* Currently reset does not clear data in v8 or spidermonkey scopes. See SECURITY-10
unique_ptr s;
s.reset( globalScriptEngine->newScope() );
s->setBoolean( "x" , true );
ASSERT( s->getBoolean( "x" ) );
s->reset();
ASSERT( !s->getBoolean( "x" ) );
*/
}
};
class FalseTests {
public:
void run() {
// Test falsy javascript values
unique_ptr s;
s.reset(globalScriptEngine->newScope());
ASSERT(!s->getBoolean("notSet"));
s->setString("emptyString", "");
ASSERT(!s->getBoolean("emptyString"));
s->setNumber("notANumberVal", std::numeric_limits::quiet_NaN());
ASSERT(!s->getBoolean("notANumberVal"));
auto obj = BSONObjBuilder().appendNull("null").obj();
s->setElement("nullVal", obj.getField("null"), obj);
ASSERT(!s->getBoolean("nullVal"));
s->setNumber("zeroVal", 0);
ASSERT(!s->getBoolean("zeroVal"));
}
};
class SimpleFunctions {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
s->invoke("x=5;", 0, 0);
ASSERT(5 == s->getNumber("x"));
s->invoke("return 17;", 0, 0);
ASSERT(17 == s->getNumber("__returnValue"));
s->invoke("function(){ return 17; }", 0, 0);
ASSERT(17 == s->getNumber("__returnValue"));
s->setNumber("x", 1.76);
s->invoke("return x == 1.76; ", 0, 0);
ASSERT(s->getBoolean("__returnValue"));
s->setNumber("x", 1.76);
s->invoke("return x == 1.79; ", 0, 0);
ASSERT(!s->getBoolean("__returnValue"));
BSONObj obj = BSON("" << 11.0);
s->invoke("function( z ){ return 5 + z; }", &obj, 0);
ASSERT_EQUALS(16, s->getNumber("__returnValue"));
}
};
/** Installs a tee for auditing log messages in the same thread. */
class LogRecordingScope {
public:
LogRecordingScope()
: _logged(false),
_threadName(mongo::getThreadName()),
_handle(mongo::logger::globalLogDomain()->attachAppender(
mongo::logger::MessageLogDomain::AppenderAutoPtr(new Tee(this)))) {}
~LogRecordingScope() {
mongo::logger::globalLogDomain()->detachAppender(_handle);
}
/** @return most recent log entry. */
bool logged() const {
return _logged;
}
private:
class Tee : public mongo::logger::MessageLogDomain::EventAppender {
public:
Tee(LogRecordingScope* scope) : _scope(scope) {}
virtual ~Tee() {}
virtual Status append(const logger::MessageEventEphemeral& event) {
// Don't want to consider logging by background threads.
if (mongo::getThreadName() == _scope->_threadName) {
_scope->_logged = true;
}
return Status::OK();
}
private:
LogRecordingScope* _scope;
};
bool _logged;
const string _threadName;
mongo::logger::MessageLogDomain::AppenderHandle _handle;
};
/** Error logging in Scope::exec(). */
class ExecLogError {
public:
void run() {
unique_ptr scope(globalScriptEngine->newScope());
// No error is logged when reportError == false.
ASSERT(!scope->exec("notAFunction()", "foo", false, false, false));
ASSERT(!_logger.logged());
// No error is logged for a valid statement.
ASSERT(scope->exec("validStatement = true", "foo", false, true, false));
ASSERT(!_logger.logged());
// An error is logged for an invalid statement when reportError == true.
ASSERT(!scope->exec("notAFunction()", "foo", false, true, false));
// Don't check if we're using SpiderMonkey. Our threading model breaks
// this test
// TODO: figure out a way to check for SpiderMonkey
auto ivs = globalScriptEngine->getInterpreterVersionString();
std::string prefix("MozJS");
if (ivs.compare(0, prefix.length(), prefix) != 0) {
ASSERT(_logger.logged());
}
}
private:
LogRecordingScope _logger;
};
/** Error logging in Scope::invoke(). */
class InvokeLogError {
public:
void run() {
unique_ptr scope(globalScriptEngine->newScope());
// No error is logged for a valid statement.
ASSERT_EQUALS(0, scope->invoke("validStatement = true", 0, 0));
ASSERT(!_logger.logged());
// An error is logged for an invalid statement.
try {
scope->invoke("notAFunction()", 0, 0);
} catch (const DBException&) {
// ignore the exception; just test that we logged something
}
// Don't check if we're using SpiderMonkey. Our threading model breaks
// this test
// TODO: figure out a way to check for SpiderMonkey
auto ivs = globalScriptEngine->getInterpreterVersionString();
std::string prefix("MozJS");
if (ivs.compare(0, prefix.length(), prefix) != 0) {
ASSERT(_logger.logged());
}
}
private:
LogRecordingScope _logger;
};
class ObjectMapping {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
BSONObj o = BSON("x" << 17.0 << "y"
<< "eliot"
<< "z"
<< "sara");
s->setObject("blah", o);
s->invoke("return blah.x;", 0, 0);
ASSERT_EQUALS(17, s->getNumber("__returnValue"));
s->invoke("return blah.y;", 0, 0);
ASSERT_EQUALS("eliot", s->getString("__returnValue"));
s->invoke("return this.z;", 0, &o);
ASSERT_EQUALS("sara", s->getString("__returnValue"));
s->invoke("return this.z == 'sara';", 0, &o);
ASSERT_EQUALS(true, s->getBoolean("__returnValue"));
s->invoke("this.z == 'sara';", 0, &o);
ASSERT_EQUALS(true, s->getBoolean("__returnValue"));
s->invoke("this.z == 'asara';", 0, &o);
ASSERT_EQUALS(false, s->getBoolean("__returnValue"));
s->invoke("return this.x == 17;", 0, &o);
ASSERT_EQUALS(true, s->getBoolean("__returnValue"));
s->invoke("return this.x == 18;", 0, &o);
ASSERT_EQUALS(false, s->getBoolean("__returnValue"));
s->invoke("function(){ return this.x == 17; }", 0, &o);
ASSERT_EQUALS(true, s->getBoolean("__returnValue"));
s->invoke("function(){ return this.x == 18; }", 0, &o);
ASSERT_EQUALS(false, s->getBoolean("__returnValue"));
s->invoke("function (){ return this.x == 17; }", 0, &o);
ASSERT_EQUALS(true, s->getBoolean("__returnValue"));
s->invoke("function z(){ return this.x == 18; }", 0, &o);
ASSERT_EQUALS(false, s->getBoolean("__returnValue"));
s->invoke("function (){ this.x == 17; }", 0, &o);
ASSERT_EQUALS(false, s->getBoolean("__returnValue"));
s->invoke("function z(){ this.x == 18; }", 0, &o);
ASSERT_EQUALS(false, s->getBoolean("__returnValue"));
s->invoke("x = 5; for( ; x <10; x++){ a = 1; }", 0, &o);
ASSERT_EQUALS(10, s->getNumber("x"));
}
};
class ObjectDecoding {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
s->invoke("z = { num : 1 };", 0, 0);
BSONObj out = s->getObject("z");
ASSERT_EQUALS(1, out["num"].number());
ASSERT_EQUALS(1, out.nFields());
s->invoke("z = { x : 'eliot' };", 0, 0);
out = s->getObject("z");
ASSERT_EQUALS((string) "eliot", out["x"].valuestr());
ASSERT_EQUALS(1, out.nFields());
BSONObj o = BSON("x" << 17);
s->setObject("blah", o);
out = s->getObject("blah");
ASSERT_EQUALS(17, out["x"].number());
}
};
class JSOIDTests {
public:
void run() {
#ifdef MOZJS
unique_ptr s(globalScriptEngine->newScope());
s->localConnect("blah");
s->invoke("z = { _id : new ObjectId() , a : 123 };", 0, 0);
BSONObj out = s->getObject("z");
ASSERT_EQUALS(123, out["a"].number());
ASSERT_EQUALS(jstOID, out["_id"].type());
OID save = out["_id"].__oid();
s->setObject("a", out);
s->invoke("y = { _id : a._id , a : 124 };", 0, 0);
out = s->getObject("y");
ASSERT_EQUALS(124, out["a"].number());
ASSERT_EQUALS(jstOID, out["_id"].type());
ASSERT_EQUALS(out["_id"].__oid().str(), save.str());
s->invoke("y = { _id : new ObjectId( a._id ) , a : 125 };", 0, 0);
out = s->getObject("y");
ASSERT_EQUALS(125, out["a"].number());
ASSERT_EQUALS(jstOID, out["_id"].type());
ASSERT_EQUALS(out["_id"].__oid().str(), save.str());
#endif
}
};
class SetImplicit {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
BSONObj o = BSON("foo"
<< "bar");
s->setObject("a.b", o);
ASSERT(s->getObject("a").isEmpty());
BSONObj o2 = BSONObj();
s->setObject("a", o2);
s->setObject("a.b", o);
ASSERT(s->getObject("a").isEmpty());
o2 = fromjson("{b:{}}");
s->setObject("a", o2);
s->setObject("a.b", o);
ASSERT(!s->getObject("a").isEmpty());
}
};
class ObjectModReadonlyTests {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
BSONObj o = BSON("x" << 17 << "y"
<< "eliot"
<< "z"
<< "sara"
<< "zz"
<< BSONObj());
s->setObject("blah", o, true);
BSONObj out;
ASSERT_THROWS(s->invoke("blah.y = 'e'", 0, 0), mongo::UserException);
ASSERT_THROWS(s->invoke("blah.a = 19;", 0, 0), mongo::UserException);
ASSERT_THROWS(s->invoke("blah.zz.a = 19;", 0, 0), mongo::UserException);
ASSERT_THROWS(s->invoke("blah.zz = { a : 19 };", 0, 0), mongo::UserException);
ASSERT_THROWS(s->invoke("delete blah['x']", 0, 0), mongo::UserException);
// read-only object itself can be overwritten
s->invoke("blah = {}", 0, 0);
out = s->getObject("blah");
ASSERT(out.isEmpty());
// test array - can't implement this in v8
// o = fromjson( "{a:[1,2,3]}" );
// s->setObject( "blah", o, true );
// out = s->getObject( "blah" );
// s->invoke( "blah.a[ 0 ] = 4;", BSONObj() );
// s->invoke( "delete blah['a'][ 2 ];", BSONObj() );
// out = s->getObject( "blah" );
// ASSERT_EQUALS( 1.0, out[ "a" ].embeddedObject()[ 0 ].number() );
// ASSERT_EQUALS( 3.0, out[ "a" ].embeddedObject()[ 2 ].number() );
}
};
class OtherJSTypes {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
{
// date
BSONObj o;
{
BSONObjBuilder b;
b.appendDate("d", Date_t::fromMillisSinceEpoch(123456789));
o = b.obj();
}
s->setObject("x", o);
s->invoke("return x.d.getTime() != 12;", 0, 0);
ASSERT_EQUALS(true, s->getBoolean("__returnValue"));
s->invoke("z = x.d.getTime();", 0, 0);
ASSERT_EQUALS(123456789, s->getNumber("z"));
s->invoke("z = { z : x.d }", 0, 0);
BSONObj out = s->getObject("z");
ASSERT(out["z"].type() == Date);
}
{
// regex
BSONObj o;
{
BSONObjBuilder b;
b.appendRegex("r", "^a", "i");
o = b.obj();
}
s->setObject("x", o);
s->invoke("z = x.r.test( 'b' );", 0, 0);
ASSERT_EQUALS(false, s->getBoolean("z"));
s->invoke("z = x.r.test( 'a' );", 0, 0);
ASSERT_EQUALS(true, s->getBoolean("z"));
s->invoke("z = x.r.test( 'ba' );", 0, 0);
ASSERT_EQUALS(false, s->getBoolean("z"));
s->invoke("z = { a : x.r };", 0, 0);
BSONObj out = s->getObject("z");
ASSERT_EQUALS((string) "^a", out["a"].regex());
ASSERT_EQUALS((string) "i", out["a"].regexFlags());
// This regex used to cause a segfault because x isn't a valid flag for a js RegExp.
// Now it throws a JS exception.
BSONObj invalidRegex = BSON_ARRAY(BSON("regex" << BSONRegEx("asdf", "x")));
const char* code =
"function (obj) {"
" var threw = false;"
" try {"
" obj.regex;" // should throw
" } catch(e) {"
" threw = true;"
" }"
" assert(threw);"
"}";
ASSERT_EQUALS(s->invoke(code, &invalidRegex, NULL), 0);
}
// array
{
BSONObj o = fromjson("{r:[1,2,3]}");
s->setObject("x", o, false);
BSONObj out = s->getObject("x");
ASSERT_EQUALS(Array, out.firstElement().type());
s->setObject("x", o, true);
out = s->getObject("x");
ASSERT_EQUALS(Array, out.firstElement().type());
}
// symbol
{
// test mutable object with symbol type
BSONObjBuilder builder;
builder.appendSymbol("sym", "value");
BSONObj in = builder.done();
s->setObject("x", in, false);
BSONObj out = s->getObject("x");
ASSERT_EQUALS(Symbol, out.firstElement().type());
// readonly
s->setObject("x", in, true);
out = s->getObject("x");
ASSERT_EQUALS(Symbol, out.firstElement().type());
}
}
};
class SpecialDBTypes {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
BSONObjBuilder b;
b.appendTimestamp("a", 123456789);
b.appendMinKey("b");
b.appendMaxKey("c");
b.append("d", Timestamp(1234, 9876));
{
BSONObj t = b.done();
ASSERT_EQUALS(Date_t::fromMillisSinceEpoch(1234000), t["d"].timestampTime());
ASSERT_EQUALS(9876U, t["d"].timestampInc());
}
s->setObject("z", b.obj());
ASSERT(s->invoke("y = { a : z.a , b : z.b , c : z.c , d: z.d }", 0, 0) == 0);
BSONObj out = s->getObject("y");
ASSERT_EQUALS(bsonTimestamp, out["a"].type());
ASSERT_EQUALS(MinKey, out["b"].type());
ASSERT_EQUALS(MaxKey, out["c"].type());
ASSERT_EQUALS(bsonTimestamp, out["d"].type());
ASSERT_EQUALS(9876U, out["d"].timestampInc());
ASSERT_EQUALS(Date_t::fromMillisSinceEpoch(1234000), out["d"].timestampTime());
ASSERT_EQUALS(Date_t::fromMillisSinceEpoch(123456789), out["a"].date());
}
};
class TypeConservation {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
// -- A --
BSONObj o;
{
BSONObjBuilder b;
b.append("a", (int)5);
b.append("b", 5.6);
o = b.obj();
}
ASSERT_EQUALS(NumberInt, o["a"].type());
ASSERT_EQUALS(NumberDouble, o["b"].type());
s->setObject("z", o);
s->invoke("return z", 0, 0);
BSONObj out = s->getObject("__returnValue");
ASSERT_EQUALS(5, out["a"].number());
ASSERT_EQUALS(5.6, out["b"].number());
ASSERT_EQUALS(NumberDouble, out["b"].type());
ASSERT_EQUALS(NumberInt, out["a"].type());
// -- B --
{
BSONObjBuilder b;
b.append("a", (int)5);
b.append("b", 5.6);
o = b.obj();
}
s->setObject("z", o, false);
s->invoke("return z", 0, 0);
out = s->getObject("__returnValue");
ASSERT_EQUALS(5, out["a"].number());
ASSERT_EQUALS(5.6, out["b"].number());
ASSERT_EQUALS(NumberDouble, out["b"].type());
ASSERT_EQUALS(NumberInt, out["a"].type());
// -- C --
{
BSONObjBuilder b;
{
BSONObjBuilder c;
c.append("0", 5.5);
c.append("1", 6);
b.appendArray("a", c.obj());
}
o = b.obj();
}
ASSERT_EQUALS(NumberDouble, o["a"].embeddedObjectUserCheck()["0"].type());
ASSERT_EQUALS(NumberInt, o["a"].embeddedObjectUserCheck()["1"].type());
s->setObject("z", o, false);
out = s->getObject("z");
ASSERT_EQUALS(NumberDouble, out["a"].embeddedObjectUserCheck()["0"].type());
ASSERT_EQUALS(NumberInt, out["a"].embeddedObjectUserCheck()["1"].type());
s->invokeSafe("z.z = 5;", 0, 0);
out = s->getObject("z");
ASSERT_EQUALS(5, out["z"].number());
ASSERT_EQUALS(NumberDouble, out["a"].embeddedObjectUserCheck()["0"].type());
// Commenting so that v8 tests will work
// TODO: this is technically bad, but here to make sure that i understand the behavior
// ASSERT_EQUALS( NumberDouble , out["a"].embeddedObjectUserCheck()["1"].type() );
// Eliot says I don't have to worry about this case
// // -- D --
//
// o = fromjson( "{a:3.0,b:4.5}" );
// ASSERT_EQUALS( NumberDouble , o["a"].type() );
// ASSERT_EQUALS( NumberDouble , o["b"].type() );
//
// s->setObject( "z" , o , false );
// s->invoke( "return z" , BSONObj() );
// out = s->getObject( "__returnValue" );
// ASSERT_EQUALS( 3 , out["a"].number() );
// ASSERT_EQUALS( 4.5 , out["b"].number() );
//
// ASSERT_EQUALS( NumberDouble , out["b"].type() );
// ASSERT_EQUALS( NumberDouble , out["a"].type() );
//
}
};
class NumberLong {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
BSONObjBuilder b;
long long val = (long long)(0xbabadeadbeefbaddULL);
b.append("a", val);
BSONObj in = b.obj();
s->setObject("a", in);
BSONObj out = s->getObject("a");
ASSERT_EQUALS(mongo::NumberLong, out.firstElement().type());
ASSERT(s->exec("b = {b:a.a}", "foo", false, true, false));
out = s->getObject("b");
ASSERT_EQUALS(mongo::NumberLong, out.firstElement().type());
if (val != out.firstElement().numberLong()) {
cout << val << endl;
cout << out.firstElement().numberLong() << endl;
cout << out.toString() << endl;
ASSERT_EQUALS(val, out.firstElement().numberLong());
}
ASSERT(s->exec("c = {c:a.a.toString()}", "foo", false, true, false));
out = s->getObject("c");
stringstream ss;
ss << "NumberLong(\"" << val << "\")";
ASSERT_EQUALS(ss.str(), out.firstElement().valuestr());
ASSERT(s->exec("d = {d:a.a.toNumber()}", "foo", false, true, false));
out = s->getObject("d");
ASSERT_EQUALS(NumberDouble, out.firstElement().type());
ASSERT_EQUALS(double(val), out.firstElement().number());
ASSERT(s->exec("e = {e:a.a.floatApprox}", "foo", false, true, false));
out = s->getObject("e");
ASSERT_EQUALS(NumberDouble, out.firstElement().type());
ASSERT_EQUALS(double(val), out.firstElement().number());
ASSERT(s->exec("f = {f:a.a.top}", "foo", false, true, false));
out = s->getObject("f");
ASSERT(NumberDouble == out.firstElement().type() || NumberInt == out.firstElement().type());
s->setObject("z", BSON("z" << (long long)(4)));
ASSERT(s->exec("y = {y:z.z.top}", "foo", false, true, false));
out = s->getObject("y");
ASSERT_EQUALS(Undefined, out.firstElement().type());
ASSERT(s->exec("x = {x:z.z.floatApprox}", "foo", false, true, false));
out = s->getObject("x");
ASSERT(NumberDouble == out.firstElement().type() || NumberInt == out.firstElement().type());
ASSERT_EQUALS(double(4), out.firstElement().number());
ASSERT(s->exec("w = {w:z.z}", "foo", false, true, false));
out = s->getObject("w");
ASSERT_EQUALS(mongo::NumberLong, out.firstElement().type());
ASSERT_EQUALS(4, out.firstElement().numberLong());
}
};
class NumberLong2 {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
BSONObj in;
{
BSONObjBuilder b;
b.append("a", 5);
b.append("b", (long long)5);
b.append("c", (long long)pow(2.0, 29));
b.append("d", (long long)pow(2.0, 30));
b.append("e", (long long)pow(2.0, 31));
b.append("f", (long long)pow(2.0, 45));
in = b.obj();
}
s->setObject("a", in);
ASSERT(s->exec("x = tojson( a ); ", "foo", false, true, false));
string outString = s->getString("x");
ASSERT(s->exec((string) "y = " + outString, "foo2", false, true, false));
BSONObj out = s->getObject("y");
ASSERT_BSONOBJ_EQ(in, out);
}
};
class NumberLongUnderLimit {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
BSONObjBuilder b;
// limit is 2^53
long long val = (long long)(9007199254740991ULL);
b.append("a", val);
BSONObj in = b.obj();
s->setObject("a", in);
BSONObj out = s->getObject("a");
ASSERT_EQUALS(mongo::NumberLong, out.firstElement().type());
ASSERT(s->exec("b = {b:a.a}", "foo", false, true, false));
out = s->getObject("b");
ASSERT_EQUALS(mongo::NumberLong, out.firstElement().type());
if (val != out.firstElement().numberLong()) {
cout << val << endl;
cout << out.firstElement().numberLong() << endl;
cout << out.toString() << endl;
ASSERT_EQUALS(val, out.firstElement().numberLong());
}
ASSERT(s->exec("c = {c:a.a.toString()}", "foo", false, true, false));
out = s->getObject("c");
stringstream ss;
ss << "NumberLong(\"" << val << "\")";
ASSERT_EQUALS(ss.str(), out.firstElement().valuestr());
ASSERT(s->exec("d = {d:a.a.toNumber()}", "foo", false, true, false));
out = s->getObject("d");
ASSERT_EQUALS(NumberDouble, out.firstElement().type());
ASSERT_EQUALS(double(val), out.firstElement().number());
ASSERT(s->exec("e = {e:a.a.floatApprox}", "foo", false, true, false));
out = s->getObject("e");
ASSERT_EQUALS(NumberDouble, out.firstElement().type());
ASSERT_EQUALS(double(val), out.firstElement().number());
ASSERT(s->exec("f = {f:a.a.top}", "foo", false, true, false));
out = s->getObject("f");
ASSERT(Undefined == out.firstElement().type());
}
};
class NumberDecimal {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
BSONObjBuilder b;
Decimal128 val = Decimal128("2.010");
b.append("a", val);
BSONObj in = b.obj();
s->setObject("a", in);
// Test the scope object
BSONObj out = s->getObject("a");
ASSERT_EQUALS(mongo::NumberDecimal, out.firstElement().type());
ASSERT_TRUE(val.isEqual(out.firstElement().numberDecimal()));
ASSERT(s->exec("b = {b:a.a}", "foo", false, true, false));
out = s->getObject("b");
ASSERT_EQUALS(mongo::NumberDecimal, out.firstElement().type());
ASSERT_TRUE(val.isEqual(out.firstElement().numberDecimal()));
// Test that the appropriate string output is generated
ASSERT(s->exec("c = {c:a.a.toString()}", "foo", false, true, false));
out = s->getObject("c");
stringstream ss;
ss << "NumberDecimal(\"" << val.toString() << "\")";
ASSERT_EQUALS(ss.str(), out.firstElement().valuestr());
}
};
class NumberDecimalGetFromScope {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
ASSERT(s->exec("a = 5;", "a", false, true, false));
ASSERT_TRUE(Decimal128(5).isEqual(s->getNumberDecimal("a")));
}
};
class NumberDecimalBigObject {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
BSONObj in;
{
BSONObjBuilder b;
b.append("a", 5);
b.append("b", Decimal128("1.5E-3000"));
b.append("c", Decimal128("1.5E-1"));
b.append("d", Decimal128("1.5E3000"));
b.append("e", Decimal128("Infinity"));
b.append("f", Decimal128("NaN"));
in = b.obj();
}
s->setObject("a", in);
ASSERT(s->exec("x = tojson( a ); ", "foo", false, true, false));
string outString = s->getString("x");
ASSERT(s->exec((string) "y = " + outString, "foo2", false, true, false));
BSONObj out = s->getObject("y");
ASSERT_BSONOBJ_EQ(in, out);
}
};
class MaxTimestamp {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
// Timestamp 't' component can exceed max for int32_t.
BSONObj in;
{
BSONObjBuilder b;
b.bb().appendNum(static_cast(bsonTimestamp));
b.bb().appendStr("a");
b.bb().appendNum(std::numeric_limits::max());
in = b.obj();
}
s->setObject("a", in);
ASSERT(s->exec("x = tojson( a ); ", "foo", false, true, false));
}
};
class WeirdObjects {
public:
BSONObj build(int depth) {
BSONObjBuilder b;
b.append("0", depth);
if (depth > 0)
b.appendArray("1", build(depth - 1));
return b.obj();
}
void run() {
unique_ptr s(globalScriptEngine->newScope());
for (int i = 5; i < 100; i += 10) {
s->setObject("a", build(i), false);
s->invokeSafe("tojson( a )", 0, 0);
s->setObject("a", build(5), true);
s->invokeSafe("tojson( a )", 0, 0);
}
}
};
/**
* Test exec() timeout value terminates execution (SERVER-8053)
*/
class ExecTimeout {
public:
void run() {
unique_ptr scope(globalScriptEngine->newScope());
// assert timeout occurred
ASSERT(!scope->exec("var a = 1; while (true) { ; }", "ExecTimeout", false, true, false, 1));
}
};
/**
* Test exec() timeout value terminates execution (SERVER-8053)
*/
class ExecNoTimeout {
public:
void run() {
unique_ptr scope(globalScriptEngine->newScope());
// assert no timeout occurred
ASSERT(scope->exec("var a = function() { return 1; }",
"ExecNoTimeout",
false,
true,
false,
5 * 60 * 1000));
}
};
/**
* Test invoke() timeout value terminates execution (SERVER-8053)
*/
class InvokeTimeout {
public:
void run() {
unique_ptr scope(globalScriptEngine->newScope());
// scope timeout after 500ms
bool caught = false;
try {
scope->invokeSafe(
"function() { "
" while (true) { } "
"} ",
0,
0,
1);
} catch (const DBException&) {
caught = true;
}
ASSERT(caught);
}
};
/**
* Test invoke() timeout value does not terminate execution (SERVER-8053)
*/
class InvokeNoTimeout {
public:
void run() {
unique_ptr scope(globalScriptEngine->newScope());
// invoke completes before timeout
scope->invokeSafe(
"function() { "
" for (var i=0; i<1; i++) { ; } "
"} ",
0,
0,
5 * 60 * 1000);
}
};
class Utf8Check {
public:
Utf8Check() {
reset();
}
~Utf8Check() {
reset();
}
void run() {
if (!globalScriptEngine->utf8Ok()) {
mongo::unittest::log() << "warning: utf8 not supported" << endl;
return;
}
string utf8ObjSpec = "{'_id':'\\u0001\\u007f\\u07ff\\uffff'}";
BSONObj utf8Obj = fromjson(utf8ObjSpec);
const ServiceContext::UniqueOperationContext txnPtr = cc().makeOperationContext();
OperationContext& txn = *txnPtr;
DBDirectClient client(&txn);
client.insert(ns(), utf8Obj);
client.eval("unittest",
"v = db.jstests.utf8check.findOne(); db.jstests.utf8check.remove( {} ); "
"db.jstests.utf8check.insert( v );");
check(utf8Obj, client.findOne(ns(), BSONObj()));
}
private:
void check(const BSONObj& one, const BSONObj& two) {
if (one.woCompare(two) != 0) {
static string fail =
string("Assertion failure expected ") + one.toString() + ", got " + two.toString();
FAIL(fail.c_str());
}
}
void reset() {
const ServiceContext::UniqueOperationContext txnPtr = cc().makeOperationContext();
OperationContext& txn = *txnPtr;
DBDirectClient client(&txn);
client.dropCollection(ns());
}
static const char* ns() {
return "unittest.jstests.utf8check";
}
};
class LongUtf8String {
public:
LongUtf8String() {
reset();
}
~LongUtf8String() {
reset();
}
void run() {
if (!globalScriptEngine->utf8Ok())
return;
const ServiceContext::UniqueOperationContext txnPtr = cc().makeOperationContext();
OperationContext& txn = *txnPtr;
DBDirectClient client(&txn);
client.eval("unittest",
"db.jstests.longutf8string.save( {_id:'\\uffff\\uffff\\uffff\\uffff'} )");
}
private:
void reset() {
const ServiceContext::UniqueOperationContext txnPtr = cc().makeOperationContext();
OperationContext& txn = *txnPtr;
DBDirectClient client(&txn);
client.dropCollection(ns());
}
static const char* ns() {
return "unittest.jstests.longutf8string";
}
};
class InvalidUTF8Check {
public:
void run() {
if (!globalScriptEngine->utf8Ok())
return;
unique_ptr s;
s.reset(globalScriptEngine->newScope());
BSONObj b;
{
char crap[5];
crap[0] = (char)128;
crap[1] = 17;
crap[2] = (char)128;
crap[3] = 17;
crap[4] = 0;
BSONObjBuilder bb;
bb.append("x", crap);
b = bb.obj();
}
// cout << "ELIOT: " << b.jsonString() << endl;
// its ok if this is handled by js, just can't create a c++ exception
s->invoke("x=this.x.length;", 0, &b);
}
};
class CodeTests {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
{
BSONObjBuilder b;
b.append("a", 1);
b.appendCode("b", "function(){ out.b = 11; }");
b.appendCodeWScope("c", "function(){ out.c = 12; }", BSONObj());
b.appendCodeWScope("d", "function(){ out.d = 13 + bleh; }", BSON("bleh" << 5));
s->setObject("foo", b.obj());
}
s->invokeSafe("out = {}; out.a = foo.a; foo.b(); foo.c();", 0, 0);
BSONObj out = s->getObject("out");
ASSERT_EQUALS(1, out["a"].number());
ASSERT_EQUALS(11, out["b"].number());
ASSERT_EQUALS(12, out["c"].number());
// Guess we don't care about this
// s->invokeSafe( "foo.d() " , BSONObj() );
// out = s->getObject( "out" );
// ASSERT_EQUALS( 18 , out["d"].number() );
}
};
namespace RoundTripTests {
// Inherit from this class to test round tripping of JSON objects
class TestRoundTrip {
public:
virtual ~TestRoundTrip() {}
void run() {
// Insert in Javascript -> Find using DBDirectClient
// Drop the collection
const ServiceContext::UniqueOperationContext txnPtr = cc().makeOperationContext();
OperationContext& txn = *txnPtr;
DBDirectClient client(&txn);
client.dropCollection("unittest.testroundtrip");
// Insert in Javascript
stringstream jsInsert;
jsInsert << "db.testroundtrip.insert(" << jsonIn() << ")";
ASSERT_TRUE(client.eval("unittest", jsInsert.str()));
// Find using DBDirectClient
BSONObj excludeIdProjection = BSON("_id" << 0);
BSONObj directFind = client.findOne("unittest.testroundtrip", "", &excludeIdProjection);
bsonEquals(bson(), directFind);
// Insert using DBDirectClient -> Find in Javascript
// Drop the collection
client.dropCollection("unittest.testroundtrip");
// Insert using DBDirectClient
client.insert("unittest.testroundtrip", bson());
// Find in Javascript
stringstream jsFind;
jsFind << "dbref = db.testroundtrip.findOne( { } , { _id : 0 } )\n"
<< "assert.eq(dbref, " << jsonOut() << ")";
ASSERT_TRUE(client.eval("unittest", jsFind.str()));
}
protected:
// Methods that must be defined by child classes
virtual BSONObj bson() const = 0;
virtual string json() const = 0;
// This can be overriden if a different meaning of equality besides woCompare is needed
virtual void bsonEquals(const BSONObj& expected, const BSONObj& actual) {
if (expected.woCompare(actual)) {
::mongo::log() << "want:" << expected.jsonString() << " size: " << expected.objsize()
<< endl;
::mongo::log() << "got :" << actual.jsonString() << " size: " << actual.objsize()
<< endl;
::mongo::log() << expected.hexDump() << endl;
::mongo::log() << actual.hexDump() << endl;
}
ASSERT(!expected.woCompare(actual));
}
// This can be overriden if the JSON representation is altered on the round trip
virtual string jsonIn() const {
return json();
}
virtual string jsonOut() const {
return json();
}
};
class DBRefTest : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
OID o;
memset(&o, 0, 12);
BSONObjBuilder subBuilder(b.subobjStart("a"));
subBuilder.append("$ref", "ns");
subBuilder.append("$id", o);
subBuilder.done();
return b.obj();
}
virtual string json() const {
return "{ \"a\" : DBRef( \"ns\", ObjectId( \"000000000000000000000000\" ) ) }";
}
// A "fetch" function is added to the DBRef object when it is inserted using the
// constructor, so we need to compare the fields individually
virtual void bsonEquals(const BSONObj& expected, const BSONObj& actual) {
ASSERT_EQUALS(expected["a"].type(), actual["a"].type());
ASSERT_EQUALS(expected["a"]["$id"].OID(), actual["a"]["$id"].OID());
ASSERT_EQUALS(expected["a"]["$ref"].String(), actual["a"]["$ref"].String());
}
};
class DBPointerTest : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
OID o;
memset(&o, 0, 12);
b.appendDBRef("a", "ns", o);
return b.obj();
}
virtual string json() const {
return "{ \"a\" : DBPointer( \"ns\", ObjectId( \"000000000000000000000000\" ) ) }";
}
};
class InformalDBRefTest : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
BSONObjBuilder subBuilder(b.subobjStart("a"));
subBuilder.append("$ref", "ns");
subBuilder.append("$id", "000000000000000000000000");
subBuilder.done();
return b.obj();
}
// Don't need to return anything because we are overriding both jsonOut and jsonIn
virtual string json() const {
return "";
}
// Need to override these because the JSON doesn't actually round trip.
// An object with "$ref" and "$id" fields is handled specially and different on the way out.
virtual string jsonOut() const {
return "{ \"a\" : DBRef( \"ns\", \"000000000000000000000000\" ) }";
}
virtual string jsonIn() const {
stringstream ss;
ss << "{ \"a\" : { \"$ref\" : \"ns\" , "
<< "\"$id\" : \"000000000000000000000000\" } }";
return ss.str();
}
};
class InformalDBRefOIDTest : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
OID o;
memset(&o, 0, 12);
BSONObjBuilder subBuilder(b.subobjStart("a"));
subBuilder.append("$ref", "ns");
subBuilder.append("$id", o);
subBuilder.done();
return b.obj();
}
// Don't need to return anything because we are overriding both jsonOut and jsonIn
virtual string json() const {
return "";
}
// Need to override these because the JSON doesn't actually round trip.
// An object with "$ref" and "$id" fields is handled specially and different on the way out.
virtual string jsonOut() const {
return "{ \"a\" : DBRef( \"ns\", ObjectId( \"000000000000000000000000\" ) ) }";
}
virtual string jsonIn() const {
stringstream ss;
ss << "{ \"a\" : { \"$ref\" : \"ns\" , "
<< "\"$id\" : ObjectId( \"000000000000000000000000\" ) } }";
return ss.str();
}
};
class InformalDBRefExtraFieldTest : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
OID o;
memset(&o, 0, 12);
BSONObjBuilder subBuilder(b.subobjStart("a"));
subBuilder.append("$ref", "ns");
subBuilder.append("$id", o);
subBuilder.append("otherfield", "value");
subBuilder.done();
return b.obj();
}
// Don't need to return anything because we are overriding both jsonOut and jsonIn
virtual string json() const {
return "";
}
// Need to override these because the JSON doesn't actually round trip.
// An object with "$ref" and "$id" fields is handled specially and different on the way out.
virtual string jsonOut() const {
return "{ \"a\" : DBRef( \"ns\", ObjectId( \"000000000000000000000000\" ) ) }";
}
virtual string jsonIn() const {
stringstream ss;
ss << "{ \"a\" : { \"$ref\" : \"ns\" , "
<< "\"$id\" : ObjectId( \"000000000000000000000000\" ) , "
<< "\"otherfield\" : \"value\" } }";
return ss.str();
}
};
class Empty : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
return b.obj();
}
virtual string json() const {
return "{}";
}
};
class EmptyWithSpace : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
return b.obj();
}
virtual string json() const {
return "{ }";
}
};
class SingleString : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.append("a", "b");
return b.obj();
}
virtual string json() const {
return "{ \"a\" : \"b\" }";
}
};
class EmptyStrings : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.append("", "");
return b.obj();
}
virtual string json() const {
return "{ \"\" : \"\" }";
}
};
class SingleNumber : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.append("a", 1);
return b.obj();
}
virtual string json() const {
return "{ \"a\" : 1 }";
}
};
class RealNumber : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
double d;
ASSERT_OK(parseNumberFromString("0.7", &d));
b.append("a", d);
return b.obj();
}
virtual string json() const {
return "{ \"a\" : 0.7 }";
}
};
class FancyNumber : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
double d;
ASSERT_OK(parseNumberFromString("-4.4433e-2", &d));
b.append("a", d);
return b.obj();
}
virtual string json() const {
return "{ \"a\" : -4.4433e-2 }";
}
};
class TwoElements : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.append("a", 1);
b.append("b", "foo");
return b.obj();
}
virtual string json() const {
return "{ \"a\" : 1, \"b\" : \"foo\" }";
}
};
class Subobject : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.append("a", 1);
BSONObjBuilder c;
c.append("z", b.done());
return c.obj();
}
virtual string json() const {
return "{ \"z\" : { \"a\" : 1 } }";
}
};
class DeeplyNestedObject : public TestRoundTrip {
virtual string buildJson(int depth) const {
if (depth == 0) {
return "{\"0\":true}";
} else {
std::stringstream ss;
ss << "{\"" << depth << "\":" << buildJson(depth - 1) << "}";
depth--;
return ss.str();
}
}
virtual BSONObj buildBson(int depth) const {
BSONObjBuilder builder;
if (depth == 0) {
builder.append("0", true);
return builder.obj();
} else {
std::stringstream ss;
ss << depth;
depth--;
builder.append(ss.str(), buildBson(depth));
return builder.obj();
}
}
virtual BSONObj bson() const {
return buildBson(35);
}
virtual string json() const {
return buildJson(35);
}
};
class ArrayEmpty : public TestRoundTrip {
virtual BSONObj bson() const {
vector arr;
BSONObjBuilder b;
b.append("a", arr);
return b.obj();
}
virtual string json() const {
return "{ \"a\" : [] }";
}
};
class Array : public TestRoundTrip {
virtual BSONObj bson() const {
vector arr;
arr.push_back(1);
arr.push_back(2);
arr.push_back(3);
BSONObjBuilder b;
b.append("a", arr);
return b.obj();
}
virtual string json() const {
return "{ \"a\" : [ 1, 2, 3 ] }";
}
};
class True : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.appendBool("a", true);
return b.obj();
}
virtual string json() const {
return "{ \"a\" : true }";
}
};
class False : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.appendBool("a", false);
return b.obj();
}
virtual string json() const {
return "{ \"a\" : false }";
}
};
class Null : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.appendNull("a");
return b.obj();
}
virtual string json() const {
return "{ \"a\" : null }";
}
};
class Undefined : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.appendUndefined("a");
return b.obj();
}
virtual string json() const {
return "{ \"a\" : undefined }";
}
};
class EscapedCharacters : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.append("a", "\" \\ / \b \f \n \r \t \v");
return b.obj();
}
virtual string json() const {
return "{ \"a\" : \"\\\" \\\\ \\/ \\b \\f \\n \\r \\t \\v\" }";
}
};
class NonEscapedCharacters : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.append("a", "% { a z $ # ' ");
return b.obj();
}
virtual string json() const {
return "{ \"a\" : \"\\% \\{ \\a \\z \\$ \\# \\' \\ \" }";
}
};
class AllowedControlCharacter : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.append("a", "\x7f");
return b.obj();
}
virtual string json() const {
return "{ \"a\" : \"\x7f\" }";
}
};
class NumbersInFieldName : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.append("b1", "b");
return b.obj();
}
virtual string json() const {
return "{ b1 : \"b\" }";
}
};
class EscapeFieldName : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.append("\n", "b");
return b.obj();
}
virtual string json() const {
return "{ \"\\n\" : \"b\" }";
}
};
class EscapedUnicodeToUtf8 : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
unsigned char u[7];
u[0] = 0xe0 | 0x0a;
u[1] = 0x80;
u[2] = 0x80;
u[3] = 0xe0 | 0x0a;
u[4] = 0x80;
u[5] = 0x80;
u[6] = 0;
b.append("a", (char*)u);
BSONObj built = b.obj();
ASSERT_EQUALS(string((char*)u), built.firstElement().valuestr());
return built;
}
virtual string json() const {
return "{ \"a\" : \"\\ua000\\uA000\" }";
}
};
class Utf8AllOnes : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
unsigned char u[8];
u[0] = 0x01;
u[1] = 0x7f;
u[2] = 0xdf;
u[3] = 0xbf;
u[4] = 0xef;
u[5] = 0xbf;
u[6] = 0xbf;
u[7] = 0;
b.append("a", (char*)u);
return b.obj();
}
virtual string json() const {
return "{ \"a\" : \"\\u0001\\u007f\\u07ff\\uffff\" }";
}
};
class Utf8FirstByteOnes : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
unsigned char u[6];
u[0] = 0xdc;
u[1] = 0x80;
u[2] = 0xef;
u[3] = 0xbc;
u[4] = 0x80;
u[5] = 0;
b.append("a", (char*)u);
return b.obj();
}
virtual string json() const {
return "{ \"a\" : \"\\u0700\\uff00\" }";
}
};
class BinData : public TestRoundTrip {
virtual BSONObj bson() const {
char z[3];
z[0] = 'a';
z[1] = 'b';
z[2] = 'c';
BSONObjBuilder b;
b.appendBinData("a", 3, BinDataGeneral, z);
return b.obj();
}
virtual string json() const {
return "{ \"a\" : BinData( 0 , \"YWJj\" ) }";
}
};
class BinDataPaddedSingle : public TestRoundTrip {
virtual BSONObj bson() const {
char z[2];
z[0] = 'a';
z[1] = 'b';
BSONObjBuilder b;
b.appendBinData("a", 2, BinDataGeneral, z);
return b.obj();
}
virtual string json() const {
return "{ \"a\" : BinData( 0 , \"YWI=\" ) }";
}
};
class BinDataPaddedDouble : public TestRoundTrip {
virtual BSONObj bson() const {
char z[1];
z[0] = 'a';
BSONObjBuilder b;
b.appendBinData("a", 1, BinDataGeneral, z);
return b.obj();
}
virtual string json() const {
return "{ \"a\" : BinData( 0 , \"YQ==\" ) }";
}
};
class BinDataAllChars : public TestRoundTrip {
virtual BSONObj bson() const {
unsigned char z[] = {0x00, 0x10, 0x83, 0x10, 0x51, 0x87, 0x20, 0x92, 0x8B, 0x30,
0xD3, 0x8F, 0x41, 0x14, 0x93, 0x51, 0x55, 0x97, 0x61, 0x96,
0x9B, 0x71, 0xD7, 0x9F, 0x82, 0x18, 0xA3, 0x92, 0x59, 0xA7,
0xA2, 0x9A, 0xAB, 0xB2, 0xDB, 0xAF, 0xC3, 0x1C, 0xB3, 0xD3,
0x5D, 0xB7, 0xE3, 0x9E, 0xBB, 0xF3, 0xDF, 0xBF};
BSONObjBuilder b;
b.appendBinData("a", 48, BinDataGeneral, z);
return b.obj();
}
virtual string json() const {
stringstream ss;
ss << "{ \"a\" : BinData( 0 , \"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
<< "abcdefghijklmnopqrstuvwxyz0123456789+/\" ) }";
return ss.str();
}
};
class Date : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.appendDate("a", Date_t());
return b.obj();
}
virtual string json() const {
return "{ \"a\" : new Date( 0 ) }";
}
};
class DateNonzero : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.appendDate("a", Date_t::fromMillisSinceEpoch(100));
return b.obj();
}
virtual string json() const {
return "{ \"a\" : new Date( 100 ) }";
}
};
class DateNegative : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.appendDate("a", Date_t::fromMillisSinceEpoch(-1));
return b.obj();
}
virtual string json() const {
return "{ \"a\" : new Date( -1 ) }";
}
};
class JSTimestamp : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.append("a", Timestamp(20, 5));
return b.obj();
}
virtual string json() const {
return "{ \"a\" : Timestamp( 20, 5 ) }";
}
};
class TimestampMax : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.appendMaxForType("a", mongo::bsonTimestamp);
BSONObj o = b.obj();
return o;
}
virtual string json() const {
Timestamp opTime = Timestamp::max();
stringstream ss;
ss << "{ \"a\" : Timestamp( " << opTime.getSecs() << ", " << opTime.getInc() << " ) }";
return ss.str();
}
};
class Regex : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.appendRegex("a", "b", "");
return b.obj();
}
virtual string json() const {
return "{ \"a\" : /b/ }";
}
};
class RegexWithQuotes : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.appendRegex("a", "\"", "");
return b.obj();
}
virtual string json() const {
return "{ \"a\" : /\"/ }";
}
};
class UnquotedFieldName : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.append("a_b", 1);
return b.obj();
}
virtual string json() const {
return "{ a_b : 1 }";
}
};
class SingleQuotes : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.append("ab'c\"", "bb\b '\"");
return b.obj();
}
virtual string json() const {
return "{ 'ab\\'c\"' : 'bb\\b \\'\"' }";
}
};
class ObjectId : public TestRoundTrip {
virtual BSONObj bson() const {
OID id;
id.init("deadbeeff00ddeadbeeff00d");
BSONObjBuilder b;
b.appendOID("foo", &id);
return b.obj();
}
virtual string json() const {
return "{ \"foo\": ObjectId( \"deadbeeff00ddeadbeeff00d\" ) }";
}
};
class NumberLong : public TestRoundTrip {
public:
virtual BSONObj bson() const {
return BSON("long" << 4611686018427387904ll); // 2**62
}
virtual string json() const {
return "{ \"long\": NumberLong(4611686018427387904) }";
}
};
class NumberInt : public TestRoundTrip {
public:
virtual BSONObj bson() const {
return BSON("int" << static_cast(100));
}
virtual string json() const {
return "{ \"int\": NumberInt(100) }";
}
};
class Number : public TestRoundTrip {
public:
virtual BSONObj bson() const {
return BSON("double" << 3.14);
}
virtual string json() const {
return "{ \"double\": Number(3.14) }";
}
};
class NumberDecimal : public TestRoundTrip {
public:
virtual BSONObj bson() const {
return BSON("decimal" << Decimal128("2.010"));
}
virtual string json() const {
return "{ \"decimal\": NumberDecimal(\"+2.010\") }";
}
};
class NumberDecimalNegative : public TestRoundTrip {
public:
virtual BSONObj bson() const {
return BSON("decimal" << Decimal128("-4.018"));
}
virtual string json() const {
return "{ \"decimal\": NumberDecimal(\"-4018E-3\") }";
}
};
class NumberDecimalMax : public TestRoundTrip {
public:
virtual BSONObj bson() const {
return BSON("decimal" << Decimal128("+9.999999999999999999999999999999999E6144"));
}
virtual string json() const {
return "{ \"decimal\": NumberDecimal(\"+9999999999999999999999999999999999E6111\") }";
}
};
class NumberDecimalMin : public TestRoundTrip {
public:
virtual BSONObj bson() const {
return BSON("decimal" << Decimal128("0.000000000000000000000000000000001E-6143"));
}
virtual string json() const {
return "{ \"decimal\": NumberDecimal(\"+1E-6176\") }";
}
};
class NumberDecimalPositiveZero : public TestRoundTrip {
public:
virtual BSONObj bson() const {
return BSON("decimal" << Decimal128("0"));
}
virtual string json() const {
return "{ \"decimal\": NumberDecimal(\"0\") }";
}
};
class NumberDecimalNegativeZero : public TestRoundTrip {
public:
virtual BSONObj bson() const {
return BSON("decimal" << Decimal128("-0"));
}
virtual string json() const {
return "{ \"decimal\": NumberDecimal(\"-0\") }";
}
};
class NumberDecimalPositiveNaN : public TestRoundTrip {
public:
virtual BSONObj bson() const {
return BSON("decimal" << Decimal128("NaN"));
}
virtual string json() const {
return "{ \"decimal\": NumberDecimal(\"NaN\") }";
}
};
class NumberDecimalNegativeNaN : public TestRoundTrip {
public:
virtual BSONObj bson() const {
return BSON("decimal" << Decimal128("-NaN"));
}
virtual string json() const {
return "{ \"decimal\": NumberDecimal(\"-NaN\") }";
}
};
class NumberDecimalPositiveInfinity : public TestRoundTrip {
public:
virtual BSONObj bson() const {
return BSON("decimal" << Decimal128("1E999999"));
}
virtual string json() const {
return "{ \"decimal\": NumberDecimal(\"+Inf\") }";
}
};
class NumberDecimalNegativeInfinity : public TestRoundTrip {
public:
virtual BSONObj bson() const {
return BSON("decimal" << Decimal128("-1E999999"));
}
virtual string json() const {
return "{ \"decimal\": NumberDecimal(\"-Inf\") }";
}
};
class NumberDecimalPrecision : public TestRoundTrip {
public:
virtual BSONObj bson() const {
return BSON("decimal" << Decimal128("5.00"));
}
virtual string json() const {
return "{ \"decimal\": NumberDecimal(\"+500E-2\") }";
}
};
class UUID : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
unsigned char z[] = {0xAB,
0xCD,
0xEF,
0xAB,
0xCD,
0xEF,
0xAB,
0xCD,
0xEF,
0xAB,
0xCD,
0xEF,
0x00,
0x00,
0x00,
0x00};
b.appendBinData("a", 16, bdtUUID, z);
return b.obj();
}
// Don't need to return anything because we are overriding both jsonOut and jsonIn
virtual string json() const {
return "";
}
// The UUID constructor corresponds to a special BinData type
virtual string jsonIn() const {
return "{ \"a\" : UUID(\"abcdefabcdefabcdefabcdef00000000\") }";
}
virtual string jsonOut() const {
return "{ \"a\" : BinData(3,\"q83vq83vq83vq83vAAAAAA==\") }";
}
};
class HexData : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
unsigned char z[] = {0xAB,
0xCD,
0xEF,
0xAB,
0xCD,
0xEF,
0xAB,
0xCD,
0xEF,
0xAB,
0xCD,
0xEF,
0x00,
0x00,
0x00,
0x00};
b.appendBinData("a", 16, BinDataGeneral, z);
return b.obj();
}
// Don't need to return anything because we are overriding both jsonOut and jsonIn
virtual string json() const {
return "";
}
// The HexData constructor creates a BinData type from a hex string
virtual string jsonIn() const {
return "{ \"a\" : HexData(0,\"abcdefabcdefabcdefabcdef00000000\") }";
}
virtual string jsonOut() const {
return "{ \"a\" : BinData(0,\"q83vq83vq83vq83vAAAAAA==\") }";
}
};
class MD5 : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
unsigned char z[] = {0xAB,
0xCD,
0xEF,
0xAB,
0xCD,
0xEF,
0xAB,
0xCD,
0xEF,
0xAB,
0xCD,
0xEF,
0x00,
0x00,
0x00,
0x00};
b.appendBinData("a", 16, MD5Type, z);
return b.obj();
}
// Don't need to return anything because we are overriding both jsonOut and jsonIn
virtual string json() const {
return "";
}
// The HexData constructor creates a BinData type from a hex string
virtual string jsonIn() const {
return "{ \"a\" : MD5(\"abcdefabcdefabcdefabcdef00000000\") }";
}
virtual string jsonOut() const {
return "{ \"a\" : BinData(5,\"q83vq83vq83vq83vAAAAAA==\") }";
}
};
class NullString : public TestRoundTrip {
virtual BSONObj bson() const {
BSONObjBuilder b;
b.append("x", "a\0b", 4);
return b.obj();
}
virtual string json() const {
return "{ \"x\" : \"a\\u0000b\" }";
}
};
} // namespace RoundTripTests
class BinDataType {
public:
void pp(const char* s, BSONElement e) {
int len;
const char* data = e.binData(len);
cout << s << ":" << e.binDataType() << "\t" << len << endl;
cout << "\t";
for (int i = 0; i < len; i++)
cout << (int)(data[i]) << " ";
cout << endl;
}
void run() {
unique_ptr s(globalScriptEngine->newScope());
const char* foo = "asdas\0asdasd";
const char* base64 = "YXNkYXMAYXNkYXNk";
BSONObj in;
{
BSONObjBuilder b;
b.append("a", 7);
b.appendBinData("b", 12, BinDataGeneral, foo);
in = b.obj();
s->setObject("x", in);
}
s->invokeSafe("myb = x.b; print( myb ); printjson( myb );", 0, 0);
s->invokeSafe("y = { c : myb };", 0, 0);
BSONObj out = s->getObject("y");
ASSERT_EQUALS(BinData, out["c"].type());
// pp( "in " , in["b"] );
// pp( "out" , out["c"] );
ASSERT_EQUALS(0, in["b"].woCompare(out["c"], false));
// check that BinData js class is utilized
s->invokeSafe("q = x.b.toString();", 0, 0);
stringstream expected;
expected << "BinData(" << BinDataGeneral << ",\"" << base64 << "\")";
ASSERT_EQUALS(expected.str(), s->getString("q"));
stringstream scriptBuilder;
scriptBuilder << "z = { c : new BinData( " << BinDataGeneral << ", \"" << base64
<< "\" ) };";
string script = scriptBuilder.str();
s->invokeSafe(script.c_str(), 0, 0);
out = s->getObject("z");
// pp( "out" , out["c"] );
ASSERT_EQUALS(0, in["b"].woCompare(out["c"], false));
s->invokeSafe("a = { f: new BinData( 128, \"\" ) };", 0, 0);
out = s->getObject("a");
int len = -1;
out["f"].binData(len);
ASSERT_EQUALS(0, len);
ASSERT_EQUALS(128, out["f"].binDataType());
}
};
class VarTests {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
ASSERT(s->exec("a = 5;", "a", false, true, false));
ASSERT_EQUALS(5, s->getNumber("a"));
ASSERT(s->exec("var b = 6;", "b", false, true, false));
ASSERT_EQUALS(6, s->getNumber("b"));
}
};
class Speed1 {
public:
void run() {
BSONObj start = BSON("x" << 5.0);
BSONObj empty;
unique_ptr s;
s.reset(globalScriptEngine->newScope());
ScriptingFunction f = s->createFunction("return this.x + 6;");
Timer t;
double n = 0;
for (; n < 10000; n++) {
s->invoke(f, &empty, &start);
ASSERT_EQUALS(11, s->getNumber("__returnValue"));
}
// cout << "speed1: " << ( n / t.millis() ) << " ops/ms" << endl;
}
};
class ScopeOut {
public:
void run() {
unique_ptr s;
s.reset(globalScriptEngine->newScope());
s->invokeSafe("x = 5;", 0, 0);
{
BSONObjBuilder b;
s->append(b, "z", "x");
ASSERT_BSONOBJ_EQ(BSON("z" << 5), b.obj());
}
s->invokeSafe("x = function(){ return 17; }", 0, 0);
BSONObj temp;
{
BSONObjBuilder b;
s->append(b, "z", "x");
temp = b.obj();
}
s->invokeSafe("foo = this.z();", 0, &temp);
ASSERT_EQUALS(17, s->getNumber("foo"));
}
};
class RenameTest {
public:
void run() {
unique_ptr s;
s.reset(globalScriptEngine->newScope());
s->setNumber("x", 5);
ASSERT_EQUALS(5, s->getNumber("x"));
ASSERT_EQUALS(Undefined, s->type("y"));
s->rename("x", "y");
ASSERT_EQUALS(5, s->getNumber("y"));
ASSERT_EQUALS(Undefined, s->type("x"));
s->rename("y", "x");
ASSERT_EQUALS(5, s->getNumber("x"));
ASSERT_EQUALS(Undefined, s->type("y"));
}
};
class InvalidStoredJS {
public:
void run() {
BSONObjBuilder query;
query.append("_id", "invalidstoredjs1");
BSONObjBuilder update;
update.append("_id", "invalidstoredjs1");
update.appendCode("value",
"function () { db.test.find().forEach(function(obj) { continue; }); }");
const ServiceContext::UniqueOperationContext txnPtr = cc().makeOperationContext();
OperationContext& txn = *txnPtr;
DBDirectClient client(&txn);
client.update("test.system.js", query.obj(), update.obj(), true /* upsert */);
unique_ptr s(globalScriptEngine->newScope());
client.eval("test", "invalidstoredjs1()");
BSONObj info;
BSONElement ret;
ASSERT(client.eval("test", "return 5 + 12", info, ret));
ASSERT_EQUALS(17, ret.number());
}
};
/**
* This tests a bug discovered in SERVER-24054, where certain interesting nan patterns crash
* spidermonkey by looking like non-double type puns. This verifies that we put that particular
* interesting nan in and that we still get a nan out.
*/
class NovelNaN {
public:
void run() {
uint8_t bits[] = {
16, 0, 0, 0, 0x01, 'a', '\0', 0x61, 0x79, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0,
};
unique_ptr s(globalScriptEngine->newScope());
s->setObject("val", BSONObj(reinterpret_cast(bits)).getOwned());
s->invoke("val[\"a\"];", 0, 0);
ASSERT_TRUE(std::isnan(s->getNumber("__returnValue")));
}
};
class NoReturnSpecified {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
s->invoke("x=5;", 0, 0);
ASSERT_EQUALS(5, s->getNumber("__returnValue"));
s->invoke("x='test'", 0, 0);
ASSERT_EQUALS("test", s->getString("__returnValue"));
s->invoke("x='return'", 0, 0);
ASSERT_EQUALS("return", s->getString("__returnValue"));
s->invoke("return 'return'", 0, 0);
ASSERT_EQUALS("return", s->getString("__returnValue"));
s->invoke("x = ' return '", 0, 0);
ASSERT_EQUALS(" return ", s->getString("__returnValue"));
s->invoke("x = \" return \"", 0, 0);
ASSERT_EQUALS(" return ", s->getString("__returnValue"));
s->invoke("x = \"' return '\"", 0, 0);
ASSERT_EQUALS("' return '", s->getString("__returnValue"));
s->invoke("x = '\" return \"'", 0, 0);
ASSERT_EQUALS("\" return \"", s->getString("__returnValue"));
s->invoke(";return 5", 0, 0);
ASSERT_EQUALS(5, s->getNumber("__returnValue"));
s->invoke("String('return')", 0, 0);
ASSERT_EQUALS("return", s->getString("__returnValue"));
s->invoke("String(' return ')", 0, 0);
ASSERT_EQUALS(" return ", s->getString("__returnValue"));
s->invoke("String(\"'return\")", 0, 0);
ASSERT_EQUALS("'return", s->getString("__returnValue"));
s->invoke("String('\"return')", 0, 0);
ASSERT_EQUALS("\"return", s->getString("__returnValue"));
}
};
class RecursiveInvoke {
public:
static BSONObj callback(const BSONObj& args, void* data) {
auto scope = static_cast(data);
scope->invoke("x = 10;", 0, 0);
return BSONObj();
}
void run() {
unique_ptr s(globalScriptEngine->newScope());
s->injectNative("foo", callback, s.get());
s->invoke("var x = 1; foo();", 0, 0);
ASSERT_EQUALS(s->getNumberInt("x"), 10);
}
};
class ErrorCodeFromInvoke {
public:
void run() {
unique_ptr s(globalScriptEngine->newScope());
{
bool threwException = false;
try {
s->invoke("\"use strict\"; x = 10;", 0, 0);
} catch (...) {
threwException = true;
auto status = exceptionToStatus();
ASSERT_EQUALS(status.code(), ErrorCodes::JSInterpreterFailure);
}
ASSERT(threwException);
}
{
bool threwException = false;
try {
s->invoke("UUID(1,2,3,4,5);", 0, 0);
} catch (...) {
threwException = true;
auto status = exceptionToStatus();
ASSERT_EQUALS(status.code(), ErrorCodes::BadValue);
}
ASSERT(threwException);
}
}
};
class All : public Suite {
public:
All() : Suite("js") {
// Initialize the Javascript interpreter
ScriptEngine::setup();
}
void setupTests() {
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
}
};
SuiteInstance myall;
} // namespace JavaJSTests