/**
* Copyright (C) 2017 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.
*/
#include "mongo/platform/basic.h"
#include
#include
#include "mongo/db/field_ref.h"
#include "mongo/db/field_ref_set.h"
#include "mongo/db/repl/idempotency_document_structure.h"
#include "mongo/db/repl/idempotency_update_sequence.h"
#include "mongo/stdx/memory.h"
#include "mongo/unittest/unittest.h"
namespace mongo {
std::vector eliminatePrefixPaths_forTest(const std::string& path,
const std::vector& paths) {
return UpdateSequenceGenerator::_eliminatePrefixPaths(path, paths);
}
size_t getPathDepth_forTest(const std::string& path) {
return UpdateSequenceGenerator::_getPathDepth(path);
}
namespace {
TEST(UpdateGenTest, FindsAllPaths) {
std::set fields{"a", "b"};
size_t depth = 1;
size_t length = 1;
PseudoRandom random(SecureRandom::create()->nextInt64());
TrivialScalarGenerator trivialScalarGenerator;
UpdateSequenceGenerator generator({fields, depth, length}, random, &trivialScalarGenerator);
ASSERT_EQ(generator.getPaths().size(), 5U);
std::vector expectedPaths{"a", "a.0", "a.b", "b", "b.0"};
std::vector foundPaths(generator.getPaths());
std::sort(expectedPaths.begin(), expectedPaths.end());
std::sort(foundPaths.begin(), foundPaths.end());
if (foundPaths != expectedPaths) {
StringBuilder sb;
sb << "Did not find all paths. Instead, we found: [ ";
bool firstIter = true;
for (auto path : foundPaths) {
if (!firstIter) {
sb << ", ";
} else {
firstIter = false;
}
sb << path;
}
sb << " ]; ";
FAIL(sb.str());
}
}
TEST(UpdateGenTest, NoDuplicatePaths) {
std::set fields{"a", "b"};
size_t depth = 2;
size_t length = 2;
PseudoRandom random(SecureRandom::create()->nextInt64());
TrivialScalarGenerator trivialScalarGenerator;
UpdateSequenceGenerator generator({fields, depth, length}, random, &trivialScalarGenerator);
auto paths = generator.getPaths();
for (size_t i = 0; i < paths.size(); i++) {
for (size_t j = i + 1; j < paths.size(); j++) {
if (paths[i] == paths[j]) {
StringBuilder sb;
sb << "Outer path matched with inner path.";
sb << generator.getPaths()[i] << " was duplicated.";
FAIL(sb.str());
}
}
}
}
TEST(UpdateGenTest, UpdatesHaveValidPaths) {
std::set fields{"a", "b"};
size_t depth = 1;
size_t length = 1;
PseudoRandom random(SecureRandom::create()->nextInt64());
TrivialScalarGenerator trivialScalarGenerator;
UpdateSequenceGenerator generator({fields, depth, length}, random, &trivialScalarGenerator);
auto update = generator.generateUpdate();
BSONObj updateArg;
if (auto setElem = update["$set"]) {
updateArg = setElem.Obj();
} else if (auto unsetElem = update["$unset"]) {
updateArg = unsetElem.Obj();
} else {
StringBuilder sb;
sb << "The generated update is not a $set or $unset BSONObj: " << update;
FAIL(sb.str());
}
std::set argPaths;
updateArg.getFieldNames(argPaths);
std::set correctPaths{"a", "b", "a.0", "a.b", "b.0"};
for (auto path : argPaths) {
FieldRef pathRef(path);
StringBuilder sb;
if (path[0] == '0' || path[0] == '1') {
sb << "Some path (" << path << "), found in the (un)set arguments from the update "
<< update << " contains a leading array position. ";
FAIL(sb.str());
}
if (correctPaths.find(path) == correctPaths.end()) {
sb << "Some path (" << path << "), found in the (un)set arguments from the update "
<< update << " contains an invalid fieldname(s). ";
FAIL(sb.str());
}
}
}
TEST(UpdateGenTest, UpdatesAreNotAmbiguous) {
std::set fields{"a", "b"};
size_t depth = 1;
size_t length = 1;
PseudoRandom random(SecureRandom::create()->nextInt64());
TrivialScalarGenerator trivialScalarGenerator;
UpdateSequenceGenerator generator({fields, depth, length}, random, &trivialScalarGenerator);
auto update = generator.generateUpdate();
BSONObj updateArg;
if (auto setElem = update["$set"]) {
updateArg = setElem.Obj();
} else if (auto unsetElem = update["$unset"]) {
updateArg = unsetElem.Obj();
} else {
StringBuilder sb;
sb << "The generated update is not a $set or $unset BSONObj: " << update;
FAIL(sb.str());
}
std::set argPathsSet;
updateArg.getFieldNames(argPathsSet);
std::vector> argPathsRefVec;
FieldRefSet pathRefSet;
for (auto path : argPathsSet) {
argPathsRefVec.push_back(stdx::make_unique(path));
const FieldRef* conflict;
if (!pathRefSet.insert(argPathsRefVec.back().get(), &conflict)) {
StringBuilder sb;
sb << "Some path in the (un)set arguments of " << update
<< " causes ambiguity due to a conflict between "
<< argPathsRefVec.back()->dottedField() << " and " << conflict->dottedField();
FAIL(sb.str());
}
}
}
std::size_t getMaxDepth(BSONObj obj) {
size_t curMaxDepth = 0;
for (auto elem : obj) {
if (elem.type() == BSONType::Object || elem.type() == BSONType::Array) {
curMaxDepth = std::max(curMaxDepth, 1 + getMaxDepth(elem.Obj()));
}
}
return curMaxDepth;
}
TEST(UpdateGenTest, UpdatesPreserveDepthConstraint) {
std::set fields{"a", "b"};
size_t depth = 2;
size_t length = 1;
PseudoRandom random(SecureRandom::create()->nextInt64());
TrivialScalarGenerator trivialScalarGenerator;
UpdateSequenceGenerator generator(
{fields, depth, length, 0.333, 0.333, 0.334}, random, &trivialScalarGenerator);
BSONElement setElem;
BSONObj update;
// Because our probabilities sum to 1, we are guaranteed to always get a $set.
update = generator.generateUpdate();
setElem = update["$set"];
BSONObj updateArg = setElem.Obj();
std::set argPaths;
updateArg.getFieldNames(argPaths);
for (auto path : argPaths) {
auto pathDepth = getPathDepth_forTest(path);
auto particularSetArgument = updateArg[path];
auto embeddedArgDepth = 0;
if (particularSetArgument.type() == BSONType::Object ||
particularSetArgument.type() == BSONType::Array) {
embeddedArgDepth = getMaxDepth(particularSetArgument.Obj()) + 1;
}
auto argDepth = pathDepth + embeddedArgDepth;
if (argDepth > depth) {
StringBuilder sb;
sb << "The path " << path << " and its argument " << particularSetArgument
<< " exceeds the maximum depth.";
FAIL(sb.str());
}
}
}
TEST(UpdateGenTest, OnlyGenerateUnset) {
std::set fields{"a", "b"};
size_t depth = 1;
size_t length = 1;
PseudoRandom random(SecureRandom::create()->nextInt64());
TrivialScalarGenerator trivialScalarGenerator;
UpdateSequenceGenerator generatorNoSet(
{fields, depth, length, 0.0, 0.0, 0.0}, random, &trivialScalarGenerator);
for (size_t i = 0; i < 100; i++) {
auto update = generatorNoSet.generateUpdate();
if (!update["$unset"]) {
StringBuilder sb;
sb << "Generator created an update that was not an $unset, even though the probability "
"of doing so is zero: "
<< update;
FAIL(sb.str());
}
}
}
TEST(UpdateGenTest, OnlySetUpdatesWithScalarValue) {
std::set fields{"a", "b"};
size_t depth = 1;
size_t length = 1;
PseudoRandom random(SecureRandom::create()->nextInt64());
TrivialScalarGenerator trivialScalarGenerator;
UpdateSequenceGenerator generatorNoUnsetAndOnlyScalar(
{fields, depth, length, 1.0, 0.0, 0.0}, random, &trivialScalarGenerator);
for (size_t i = 0; i < 100; i++) {
auto update = generatorNoUnsetAndOnlyScalar.generateUpdate();
if (!update["$set"]) {
StringBuilder sb;
sb << "Generator created an update that was not an $set, even though the probability "
"of doing so is zero: "
<< update;
FAIL(sb.str());
} else if (getMaxDepth(update["$set"].Obj()) != 0) {
StringBuilder sb;
sb << "Generator created an update that had a nonscalar value, because it's maximum "
"depth was nonzero: "
<< update;
FAIL(sb.str());
}
}
}
TEST(UpdateGenTest, OnlySetUpdatesWithScalarsAtMaxDepth) {
std::set fields{"a", "b"};
size_t depth = 2;
size_t length = 1;
PseudoRandom random(SecureRandom::create()->nextInt64());
TrivialScalarGenerator trivialScalarGenerator;
UpdateSequenceGenerator generatorNeverScalar(
{fields, depth, length, 0.0, 0.5, 0.5}, random, &trivialScalarGenerator);
for (size_t i = 0; i < 100; i++) {
auto update = generatorNeverScalar.generateUpdate();
for (auto elem : update["$set"].Obj()) {
StringData fieldName = elem.fieldNameStringData();
FieldRef fieldRef(fieldName);
size_t pathDepth = getPathDepth_forTest(fieldName.toString());
bool isDocOrArr = elem.type() == BSONType::Object || elem.type() == BSONType::Array;
if (pathDepth != depth) {
// If the path is not equal to the max depth we provided above, then there
// should
// only be an array or doc at this point.
if (!isDocOrArr) {
StringBuilder sb;
sb << "The set argument: " << elem
<< " is a scalar, but the probability of a scalar occuring for a path that "
"does not meet the maximum depth is zero.";
FAIL(sb.str());
}
} else {
if (isDocOrArr) {
StringBuilder sb;
sb << "The set argument: " << elem
<< " is not scalar, however, this path reaches the maximum depth so a "
"scalar should be the only choice.";
FAIL(sb.str());
}
}
}
}
}
} // namespace
} // namespace mongo