// accumulatortests.cpp : Unit tests for Accumulator classes. /** * Copyright (C) 2012 10gen 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/pch.h" #include "mongo/db/pipeline/accumulator.h" #include "mongo/db/pipeline/document.h" #include "mongo/db/pipeline/expression_context.h" #include "mongo/dbtests/dbtests.h" namespace AccumulatorTests { class Base { protected: BSONObj fromDocument( const Document& document ) { return document.toBson(); } BSONObj fromValue( const Value& value ) { BSONObjBuilder bob; value.addToBsonObj( &bob, "" ); return bob.obj(); } /** Check binary equality, ensuring use of the same numeric types. */ void assertBinaryEqual( const BSONObj& expected, const BSONObj& actual ) const { ASSERT_EQUALS( expected, actual ); ASSERT( expected.binaryEqual( actual ) ); } private: intrusive_ptr _shard; intrusive_ptr _router; }; namespace Avg { class Base : public AccumulatorTests::Base { public: virtual ~Base() { } protected: void createAccumulator() { _accumulator = AccumulatorAvg::create(); ASSERT_EQUALS(string("$avg"), _accumulator->getOpName()); } Accumulator *accumulator() { return _accumulator.get(); } private: intrusive_ptr _accumulator; }; /** No documents evaluated. */ class None : public Base { public: void run() { createAccumulator(); ASSERT_EQUALS( 0, accumulator()->getValue(false).getDouble() ); } }; /** One int value is converted to double. */ class OneInt : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(3), false); ASSERT_EQUALS( 3, accumulator()->getValue(false).getDouble() ); } }; /** One long value is converted to double. */ class OneLong : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(-4LL), false); ASSERT_EQUALS( -4, accumulator()->getValue(false).getDouble() ); } }; /** One double value. */ class OneDouble : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(22.6), false); ASSERT_EQUALS( 22.6, accumulator()->getValue(false).getDouble() ); } }; /** The average of two ints is an int, even if inexact. */ class IntInt : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(10), false); accumulator()->process(Value(11), false); ASSERT_EQUALS( 10.5, accumulator()->getValue(false).getDouble() ); } }; /** The average of an int and a double is calculated as a double. */ class IntDouble : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(10), false); accumulator()->process(Value(11.0), false); ASSERT_EQUALS( 10.5, accumulator()->getValue(false).getDouble() ); } }; /** Unlike $sum, two ints do not overflow in the 'total' portion of the average. */ class IntIntNoOverflow : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(numeric_limits::max()), false); accumulator()->process(Value(numeric_limits::max()), false); ASSERT_EQUALS(numeric_limits::max(), accumulator()->getValue(false).getDouble()); } }; /** Two longs do overflow in the 'total' portion of the average. */ class LongLongOverflow : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(numeric_limits::max()), false); accumulator()->process(Value(numeric_limits::max()), false); ASSERT_EQUALS( ( (double)numeric_limits::max() + numeric_limits::max() ) / 2.0, accumulator()->getValue(false).getDouble() ); } }; namespace Shard { class SingleOperandBase : public Base { public: void run() { createAccumulator(); accumulator()->process(operand(), false); assertBinaryEqual( expectedResult(), fromDocument(accumulator()->getValue(true).getDocument())); } protected: virtual Value operand() = 0; virtual BSONObj expectedResult() = 0; }; /** Shard result for one integer. */ class Int : public SingleOperandBase { Value operand() { return Value(3); } BSONObj expectedResult() { return BSON( "subTotal" << 3.0 << "count" << 1LL ); } }; /** Shard result for one long. */ class Long : public SingleOperandBase { Value operand() { return Value(5LL); } BSONObj expectedResult() { return BSON( "subTotal" << 5.0 << "count" << 1LL ); } }; /** Shard result for one double. */ class Double : public SingleOperandBase { Value operand() { return Value(116.0); } BSONObj expectedResult() { return BSON( "subTotal" << 116.0 << "count" << 1LL ); } }; class TwoOperandBase : public Base { public: void run() { checkAvg( operand1(), operand2() ); checkAvg( operand2(), operand1() ); } protected: virtual Value operand1() = 0; virtual Value operand2() = 0; virtual BSONObj expectedResult() = 0; private: void checkAvg( const Value& a, const Value& b ) { createAccumulator(); accumulator()->process(a, false); accumulator()->process(b, false); assertBinaryEqual(expectedResult(), fromDocument(accumulator()->getValue(true).getDocument())); } }; /** Shard two ints overflow. */ class IntIntOverflow : public TwoOperandBase { Value operand1() { return Value(numeric_limits::max()); } Value operand2() { return Value(3); } BSONObj expectedResult() { return BSON( "subTotal" << numeric_limits::max() + 3.0 << "count" << 2LL ); } }; /** Shard avg an int and a long. */ class IntLong : public TwoOperandBase { Value operand1() { return Value(5); } Value operand2() { return Value(3LL); } BSONObj expectedResult() { return BSON( "subTotal" << 8.0 << "count" << 2LL ); } }; /** Shard avg an int and a double. */ class IntDouble : public TwoOperandBase { Value operand1() { return Value(5); } Value operand2() { return Value(6.2); } BSONObj expectedResult() { return BSON( "subTotal" << 11.2 << "count" << 2LL ); } }; /** Shard avg a long and a double. */ class LongDouble : public TwoOperandBase { Value operand1() { return Value(5LL); } Value operand2() { return Value(1.0); } BSONObj expectedResult() { return BSON( "subTotal" << 6.0 << "count" << 2LL ); } }; /** Shard avg an int, long, and double. */ class IntLongDouble : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(1), false); accumulator()->process(Value(2LL), false); accumulator()->process(Value(4.0), false); assertBinaryEqual(BSON( "subTotal" << 7.0 << "count" << 3LL ), fromDocument(accumulator()->getValue(true).getDocument())); } }; } // namespace Shard namespace Router { /** Router result from one shard. */ class OneShard : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(DOC("subTotal" << 3.0 << "count" << 2LL)), true); assertBinaryEqual( BSON( "" << 3.0 / 2 ), fromValue( accumulator()->getValue(false) ) ); } }; /** Router result from two shards. */ class TwoShards : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(DOC("subTotal" << 6.0 << "count" << 1LL)), true); accumulator()->process(Value(DOC("subTotal" << 5.0 << "count" << 2LL)), true); assertBinaryEqual( BSON( "" << 11.0 / 3 ), fromValue( accumulator()->getValue(false) ) ); } }; } // namespace Router } // namespace Avg namespace First { class Base : public AccumulatorTests::Base { protected: void createAccumulator() { _accumulator = AccumulatorFirst::create(); ASSERT_EQUALS(string("$first"), _accumulator->getOpName()); } Accumulator *accumulator() { return _accumulator.get(); } private: intrusive_ptr _accumulator; }; /** The accumulator evaluates no documents. */ class None : public Base { public: void run() { createAccumulator(); // The accumulator returns no value in this case. ASSERT( accumulator()->getValue(false).missing() ); } }; /* The accumulator evaluates one document and retains its value. */ class One : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(5), false); ASSERT_EQUALS( 5, accumulator()->getValue(false).getInt() ); } }; /* The accumulator evaluates one document with the field missing, returns missing value. */ class Missing : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(), false); ASSERT_EQUALS( EOO, accumulator()->getValue(false).getType() ); } }; /* The accumulator evaluates two documents and retains the value in the first. */ class Two : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(5), false); accumulator()->process(Value(7), false); ASSERT_EQUALS( 5, accumulator()->getValue(false).getInt() ); } }; /* The accumulator evaluates two documents and retains the missing value in the first. */ class FirstMissing : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(), false); accumulator()->process(Value(7), false); ASSERT_EQUALS( EOO, accumulator()->getValue(false).getType() ); } }; } // namespace First namespace Last { class Base : public AccumulatorTests::Base { protected: void createAccumulator() { _accumulator = AccumulatorLast::create(); ASSERT_EQUALS(string("$last"), _accumulator->getOpName()); } Accumulator *accumulator() { return _accumulator.get(); } private: intrusive_ptr _accumulator; }; /** The accumulator evaluates no documents. */ class None : public Base { public: void run() { createAccumulator(); // The accumulator returns no value in this case. ASSERT( accumulator()->getValue(false).missing() ); } }; /* The accumulator evaluates one document and retains its value. */ class One : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(5), false); ASSERT_EQUALS( 5, accumulator()->getValue(false).getInt() ); } }; /* The accumulator evaluates one document with the field missing retains undefined. */ class Missing : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(), false); ASSERT_EQUALS( EOO , accumulator()->getValue(false).getType() ); } }; /* The accumulator evaluates two documents and retains the value in the last. */ class Two : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(5), false); accumulator()->process(Value(7), false); ASSERT_EQUALS( 7, accumulator()->getValue(false).getInt() ); } }; /* The accumulator evaluates two documents and retains the undefined value in the last. */ class LastMissing : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(7), false); accumulator()->process(Value(), false); ASSERT_EQUALS( EOO , accumulator()->getValue(false).getType() ); } }; } // namespace Last namespace Min { class Base : public AccumulatorTests::Base { protected: void createAccumulator() { _accumulator = AccumulatorMinMax::createMin(); ASSERT_EQUALS(string("$min"), _accumulator->getOpName()); } Accumulator *accumulator() { return _accumulator.get(); } private: intrusive_ptr _accumulator; }; /** The accumulator evaluates no documents. */ class None : public Base { public: void run() { createAccumulator(); // The accumulator returns no value in this case. ASSERT( accumulator()->getValue(false).missing() ); } }; /* The accumulator evaluates one document and retains its value. */ class One : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(5), false); ASSERT_EQUALS( 5, accumulator()->getValue(false).getInt() ); } }; /* The accumulator evaluates one document with the field missing retains undefined. */ class Missing : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(), false); ASSERT_EQUALS( EOO , accumulator()->getValue(false).getType() ); } }; /* The accumulator evaluates two documents and retains the minimum value. */ class Two : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(5), false); accumulator()->process(Value(7), false); ASSERT_EQUALS( 5, accumulator()->getValue(false).getInt() ); } }; /* The accumulator evaluates two documents and retains the undefined value. */ class LastMissing : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(7), false); accumulator()->process(Value(), false); ASSERT_EQUALS( 7 , accumulator()->getValue(false).getInt() ); } }; } // namespace Min namespace Max { class Base : public AccumulatorTests::Base { protected: void createAccumulator() { _accumulator = AccumulatorMinMax::createMax(); ASSERT_EQUALS(string("$max"), _accumulator->getOpName()); } Accumulator *accumulator() { return _accumulator.get(); } private: intrusive_ptr _accumulator; }; /** The accumulator evaluates no documents. */ class None : public Base { public: void run() { createAccumulator(); // The accumulator returns no value in this case. ASSERT( accumulator()->getValue(false).missing() ); } }; /* The accumulator evaluates one document and retains its value. */ class One : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(5), false); ASSERT_EQUALS( 5, accumulator()->getValue(false).getInt() ); } }; /* The accumulator evaluates one document with the field missing retains undefined. */ class Missing : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(), false); ASSERT_EQUALS( EOO, accumulator()->getValue(false).getType() ); } }; /* The accumulator evaluates two documents and retains the maximum value. */ class Two : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(5), false); accumulator()->process(Value(7), false); ASSERT_EQUALS( 7, accumulator()->getValue(false).getInt() ); } }; /* The accumulator evaluates two documents and retains the defined value. */ class LastMissing : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(7), false); accumulator()->process(Value(), false); ASSERT_EQUALS( 7, accumulator()->getValue(false).getInt() ); } }; } // namespace Max namespace Sum { class Base : public AccumulatorTests::Base { protected: void createAccumulator() { _accumulator = AccumulatorSum::create(); ASSERT_EQUALS(string("$sum"), _accumulator->getOpName()); } Accumulator *accumulator() { return _accumulator.get(); } private: intrusive_ptr _accumulator; }; /** No documents evaluated. */ class None : public Base { public: void run() { createAccumulator(); ASSERT_EQUALS( 0, accumulator()->getValue(false).getInt() ); } }; /** An int. */ class OneInt : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(5), false); ASSERT_EQUALS( 5, accumulator()->getValue(false).getInt() ); } }; /** A long. */ class OneLong : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(6LL), false); ASSERT_EQUALS( 6, accumulator()->getValue(false).getLong() ); } }; /** A long that cannot be expressed as an int. */ class OneLageLong : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(60000000000LL), false); ASSERT_EQUALS( 60000000000LL, accumulator()->getValue(false).getLong() ); } }; /** A double. */ class OneDouble : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(7.0), false); ASSERT_EQUALS( 7.0, accumulator()->getValue(false).getDouble() ); } }; /** A non integer valued double. */ class OneFractionalDouble : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(7.5), false); ASSERT_EQUALS( 7.5, accumulator()->getValue(false).getDouble() ); } }; /** A nan double. */ class OneNanDouble : public Base { public: void run() { createAccumulator(); accumulator()->process(Value(numeric_limits::quiet_NaN()), false); // NaN is unequal to itself. ASSERT_NOT_EQUALS( accumulator()->getValue(false).getDouble(), accumulator()->getValue(false).getDouble() ); } }; class TypeConversionBase : public Base { public: virtual ~TypeConversionBase() { } void run() { checkPairSum( summand1(), summand2() ); checkPairSum( summand2(), summand1() ); } protected: virtual Value summand1() { verify( false ); } virtual Value summand2() { verify( false ); } virtual Value expectedSum() = 0; void checkPairSum( Value first, Value second ) { createAccumulator(); accumulator()->process(first, false); accumulator()->process(second, false); checkSum(); } void checkSum() { Value result = accumulator()->getValue(false); ASSERT_EQUALS( expectedSum(), result ); ASSERT_EQUALS( expectedSum().getType(), result.getType() ); } }; /** Two ints are summed. */ class IntInt : public TypeConversionBase { Value summand1() { return Value(4); } Value summand2() { return Value(5); } Value expectedSum() { return Value(9); } }; /** Two ints overflow. */ class IntIntOverflow : public TypeConversionBase { Value summand1() { return Value(numeric_limits::max()); } Value summand2() { return Value(10); } Value expectedSum() { return Value(numeric_limits::max() + 10LL); } }; /** Two ints negative overflow. */ class IntIntNegativeOverflow : public TypeConversionBase { Value summand1() { return Value(-numeric_limits::max()); } Value summand2() { return Value(-10); } Value expectedSum() { return Value(-numeric_limits::max() + -10LL); } }; /** An int and a long are summed. */ class IntLong : public TypeConversionBase { Value summand1() { return Value(4); } Value summand2() { return Value(5LL); } Value expectedSum() { return Value(9LL); } }; /** An int and a long do not trigger an int overflow. */ class IntLongNoIntOverflow : public TypeConversionBase { Value summand1() { return Value(numeric_limits::max()); } Value summand2() { return Value(1LL); } Value expectedSum() { return Value((long long)numeric_limits::max() + 1); } }; /** An int and a long overflow. */ class IntLongLongOverflow : public TypeConversionBase { Value summand1() { return Value(1); } Value summand2() { return Value(numeric_limits::max()); } Value expectedSum() { return Value(numeric_limits::max() + 1); } }; /** Two longs are summed. */ class LongLong : public TypeConversionBase { Value summand1() { return Value(4LL); } Value summand2() { return Value(5LL); } Value expectedSum() { return Value(9LL); } }; /** Two longs overflow. */ class LongLongOverflow : public TypeConversionBase { Value summand1() { return Value(numeric_limits::max()); } Value summand2() { return Value(numeric_limits::max()); } Value expectedSum() { return Value(numeric_limits::max() + numeric_limits::max()); } }; /** An int and a double are summed. */ class IntDouble : public TypeConversionBase { Value summand1() { return Value(4); } Value summand2() { return Value(5.5); } Value expectedSum() { return Value(9.5); } }; /** An int and a NaN double are summed. */ class IntNanDouble : public TypeConversionBase { Value summand1() { return Value(4); } Value summand2() { return Value(numeric_limits::quiet_NaN()); } Value expectedSum() { // BSON compares NaN values as equal. return Value(numeric_limits::quiet_NaN()); } }; /** An int and a NaN sum to NaN. */ class IntDoubleNoIntOverflow : public TypeConversionBase { Value summand1() { return Value(numeric_limits::max()); } Value summand2() { return Value(1.0); } Value expectedSum() { return Value((long long)numeric_limits::max() + 1.0); } }; /** A long and a double are summed. */ class LongDouble : public TypeConversionBase { Value summand1() { return Value(4LL); } Value summand2() { return Value(5.5); } Value expectedSum() { return Value(9.5); } }; /** A long and a double do not trigger a long overflow. */ class LongDoubleNoLongOverflow : public TypeConversionBase { Value summand1() { return Value(numeric_limits::max()); } Value summand2() { return Value(1.0); } Value expectedSum() { return Value((long long)numeric_limits::max() + 1.0); } }; /** Two double values are summed. */ class DoubleDouble : public TypeConversionBase { Value summand1() { return Value(2.5); } Value summand2() { return Value(5.5); } Value expectedSum() { return Value(8.0); } }; /** Two double values overflow. */ class DoubleDoubleOverflow : public TypeConversionBase { Value summand1() { return Value(numeric_limits::max()); } Value summand2() { return Value(numeric_limits::max()); } Value expectedSum() { return Value(numeric_limits::infinity()); } }; /** Three values, an int, a long, and a double, are summed. */ class IntLongDouble : public TypeConversionBase { public: void run() { createAccumulator(); accumulator()->process(Value(5), false); accumulator()->process(Value(99), false); accumulator()->process(Value(0.2), false); checkSum(); } private: Value expectedSum() { return Value(104.2); } }; /** A negative value is summed. */ class Negative : public TypeConversionBase { Value summand1() { return Value(5); } Value summand2() { return Value(-8.8); } Value expectedSum() { return Value(5 - 8.8); } }; /** A long and a negative int are is summed. */ class LongIntNegative : public TypeConversionBase { Value summand1() { return Value(5LL); } Value summand2() { return Value(-6); } Value expectedSum() { return Value(-1LL); } }; /** A null value is summed as zero. */ class IntNull : public TypeConversionBase { Value summand1() { return Value(5); } Value summand2() { return Value(BSONNULL); } Value expectedSum() { return Value(5); } }; /** An undefined value is summed as zero. */ class IntUndefined : public TypeConversionBase { Value summand1() { return Value(9); } Value summand2() { return Value(); } Value expectedSum() { return Value(9); } }; /** Two large integers do not overflow if a double is added later. */ class NoOverflowBeforeDouble : public TypeConversionBase { public: void run() { createAccumulator(); accumulator()->process(Value(numeric_limits::max()), false); accumulator()->process(Value(numeric_limits::max()), false); accumulator()->process(Value(1.0), false); checkSum(); } private: Value expectedSum() { return Value((double)numeric_limits::max() + (double)numeric_limits::max()); } }; } // namespace Sum class All : public Suite { public: All() : Suite( "accumulator" ) { } 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(); } } myall; } // namespace AccumulatorTests