diff --git a/indra/llmath/tests/llbbox_test.cpp b/indra/llmath/tests/llbbox_test.cpp index 726bf6d8ff..e88e98b2cf 100644 --- a/indra/llmath/tests/llbbox_test.cpp +++ b/indra/llmath/tests/llbbox_test.cpp @@ -144,10 +144,11 @@ namespace CHECK(bbox1.getExtentLocal() == LLVector3(2.0f, 2.0f, 2.0f)); + // Local: the rotation and position do not enter into it. LLBBox bbox2(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(ANGLE, LLVector3(0.0f, 0.0f, 1.0f)), - LLVector3(2.0f, 2.0f, 2.0f), LLVector3(4.0f, 4.0f, 4.0f)); + LLVector3(2.0f, 2.0f, 2.0f), LLVector3(4.0f, 5.0f, 6.0f)); - CHECK(bbox1.getExtentLocal() == LLVector3(2.0f, 2.0f, 2.0f)); + CHECK(bbox2.getExtentLocal() == LLVector3(2.0f, 3.0f, 4.0f)); } TEST_CASE("LLBBox: addPointLocal grows the box", "[LLBBox]") @@ -217,9 +218,6 @@ namespace TEST_CASE("LLBBox: addBBoxLocal after addPointAgent on a rotated box", "[LLBBox]") { - // - // test the addBBoxAgent() method - // LLBBox bbox1(LLVector3(1.0f, 1.0f, 1.0f), LLQuaternion(1.0, 0.0, 0.0, 1.0), LLVector3(2.0f, 2.0f, 2.0f), LLVector3(4.0f, 4.0f, 4.0f)); @@ -236,6 +234,36 @@ namespace CHECK(bbox1.getMaxLocal() == LLVector3(10.0f, 10.0f, 10.0f)); } + TEST_CASE("LLBBox: addBBoxAgent into an empty box takes the other box's frame", "[LLBBox]") + { + const LLQuaternion quarter(ANGLE, LLVector3(0.0f, 0.0f, 1.0f)); + LLBBox bbox1; + bbox1.addBBoxAgent(LLBBox(LLVector3(1.0f, 2.0f, 3.0f), quarter, + LLVector3(0.0f, 0.0f, 0.0f), LLVector3(2.0f, 4.0f, 6.0f))); + + CHECK(bbox1.getPositionAgent() == LLVector3(1.0f, 2.0f, 3.0f)); + CHECK(bbox1.getRotation() == quarter); + CHECK(APPROX_EQUAL(bbox1.getMinLocal(), LLVector3(0.0f, 0.0f, 0.0f))); + CHECK(APPROX_EQUAL(bbox1.getMaxLocal(), LLVector3(2.0f, 4.0f, 6.0f))); + } + + TEST_CASE("LLBBox: addBBoxAgent grows a box by the other box's corners in agent space", "[LLBBox]") + { + // An axis-aligned box at the origin, holding only the origin. + LLBBox bbox1; + bbox1.addPointLocal(LLVector3(0.0f, 0.0f, 0.0f)); + + // Two by one by one, turned a quarter about z, at (10, 0, 0): x goes + // to y and y to -x, so its corners span x 9 to 10, y 0 to 2, z 0 to 1. + bbox1.addBBoxAgent(LLBBox(LLVector3(10.0f, 0.0f, 0.0f), LLQuaternion(ANGLE, LLVector3(0.0f, 0.0f, 1.0f)), + LLVector3(0.0f, 0.0f, 0.0f), LLVector3(2.0f, 1.0f, 1.0f))); + + CHECK(bbox1.getPositionAgent() == LLVector3(0.0f, 0.0f, 0.0f)); + CHECK(bbox1.getRotation() == LLQuaternion()); + CHECK(APPROX_EQUAL(bbox1.getMinLocal(), LLVector3(0.0f, 0.0f, 0.0f))); + CHECK(APPROX_EQUAL(bbox1.getMaxLocal(), LLVector3(10.0f, 2.0f, 1.0f))); + } + TEST_CASE("LLBBox: expand by zero then a positive then a negative amount", "[LLBBox]") { // diff --git a/indra/llmath/tests/llbboxlocal_test.cpp b/indra/llmath/tests/llbboxlocal_test.cpp index df9694e905..d29df8ed58 100644 --- a/indra/llmath/tests/llbboxlocal_test.cpp +++ b/indra/llmath/tests/llbboxlocal_test.cpp @@ -114,13 +114,17 @@ namespace // test the getExtent() method // - LLBBoxLocal bbox2(LLVector3(0.0f, 0.0f, 0.0f), LLVector3(-1.0f, -1.0f, -1.0f)); - CHECK(LLBBoxLocal().getExtent() == LLVector3(0.0f, 0.0f, 0.0f)); LLBBoxLocal bbox3(LLVector3(-1.0f, -1.0f, -1.0f), LLVector3(1.0f, 2.0f, 0.0f)); CHECK(bbox3.getExtent() == LLVector3(2.0f, 3.0f, 1.0f)); + + // Max less min, so negative where min is above max. + LLBBoxLocal bbox2(LLVector3(0.0f, 0.0f, 0.0f), LLVector3(-1.0f, -1.0f, -1.0f)); + + UNSCOPED_INFO("min above max"); + CHECK(bbox2.getExtent() == LLVector3(-1.0f, -1.0f, -1.0f)); } TEST_CASE("LLBBoxLocal: addPoint grows the box only for points outside it", "[LLBBoxLocal]") diff --git a/indra/llmath/tests/llquaternion_test.cpp b/indra/llmath/tests/llquaternion_test.cpp index 5a5bac459c..e9f5dc327c 100644 --- a/indra/llmath/tests/llquaternion_test.cpp +++ b/indra/llmath/tests/llquaternion_test.cpp @@ -28,6 +28,7 @@ #include "linden_common.h" #include +#include #include #include "../v4math.h" @@ -49,7 +50,45 @@ namespace CHECK(llquat.mQ[3] == 1.f); } + // The rotation of a matrix comes back as the quaternion it was made from, + // or its negation, which is the same rotation. + void require_same_rotation(const LLQuaternion& actual, const LLQuaternion& expected) + { + using Catch::Matchers::WithinAbs; + INFO("expected " << expected << ", got " << actual); + const F32 sign = dot(actual, expected) < 0.f ? -1.f : 1.f; + for (int i = 0; i < 4; ++i) + { + CAPTURE(i); + REQUIRE_THAT(actual.mQ[i], WithinAbs(sign * expected.mQ[i], 1e-6)); + } + } + //test case for explicit LLQuaternion(const LLMatrix4 &mat) fn. + TEST_CASE("LLQuaternion: from an LLMatrix4, the matrix's rotation, its translation ignored", "[LLQuaternion]") + { + CHECK(LLQuaternion(LLMatrix4()) == LLQuaternion::DEFAULT); + + const LLQuaternion turn(1.1f, LLVector3(2.f, -1.f, 3.f)); + require_same_rotation(LLQuaternion(LLMatrix4(turn)), turn); + require_same_rotation(LLQuaternion(LLMatrix4(turn, LLVector4(5.f, -6.f, 7.f, 1.f))), turn); + + // Past a half turn, where w is negative. + const LLQuaternion wide(4.f, LLVector3(-1.f, 0.5f, 0.25f)); + require_same_rotation(LLQuaternion(LLMatrix4(wide)), wide); + } + + //test case for explicit LLQuaternion(const LLMatrix3 &mat) fn. + TEST_CASE("LLQuaternion: from an LLMatrix3, the matrix's rotation", "[LLQuaternion]") + { + CHECK(LLQuaternion(LLMatrix3()) == LLQuaternion::DEFAULT); + + const LLQuaternion turn(1.1f, LLVector3(2.f, -1.f, 3.f)); + require_same_rotation(LLQuaternion(LLMatrix3(turn)), turn); + const LLQuaternion wide(4.f, LLVector3(-1.f, 0.5f, 0.25f)); + require_same_rotation(LLQuaternion(LLMatrix3(wide)), wide); + } + TEST_CASE("LLQuaternion: LLMatrix4 initRows fills the matrix row by row", "[LLQuaternion]") { LLMatrix4 llmat; @@ -122,12 +161,6 @@ namespace res_quat.setQuatInit(x_val, y_val, z_val, w_val); res_quat.normQuat(); - CHECK(is_approx_equal(0.0f, res_quat.mQ[0])); - CHECK(is_approx_equal(0.0f, res_quat.mQ[1])); - CHECK(is_approx_equal(0.0f, res_quat.mQ[2])); - CHECK(is_approx_equal(1.0f, res_quat.mQ[3])); - - CHECK(is_approx_equal(0.0f, res_quat.mQ[0])); CHECK(is_approx_equal(0.0f, res_quat.mQ[1])); CHECK(is_approx_equal(0.0f, res_quat.mQ[2])); @@ -229,7 +262,7 @@ namespace CHECK(is_approx_equal_fraction(0.283559f, res_nlerp.mQ[2], 16)); CHECK(is_approx_equal_fraction(0.898506f, res_nlerp.mQ[3], 16)); - //test case for nlerp(F32 t, const LLQuaternion &q) fn. + //test case for slerp(F32 u, const LLQuaternion &q) fn. res_slerp = slerp(value1, quat1); CHECK(is_approx_equal_fraction(1.0f, res_slerp.mQ[0], 16)); CHECK(is_approx_equal_fraction(2.0f, res_slerp.mQ[1], 16)); @@ -519,9 +552,10 @@ namespace result = OrderToString(LLQuaternion::ZXY); CHECK(std::string_view(result) == "ZXY"); CHECK(std::string_view(result) != "XYZ"); + CHECK(std::string_view(result) != "YZX"); + CHECK(std::string_view(result) != "XZY"); CHECK(std::string_view(result) != "YXZ"); CHECK(std::string_view(result) != "ZYX"); - CHECK(std::string_view(result) != "XYZ"); result = OrderToString(LLQuaternion::XZY); CHECK(std::string_view(result) == "XZY"); @@ -569,19 +603,33 @@ namespace CHECK(is_approx_equal_fraction(0.945733f, vect.mV[0], 16)); } - TEST_CASE("LLQuaternion: getEulerAngles leaves an identity quaternion unchanged", "[LLQuaternion]") + TEST_CASE("LLQuaternion: getEulerAngles gives back the angles setEulerAngles took", "[LLQuaternion]") { //test case for void LLQuaternion::getEulerAngles(F32 *roll, F32 *pitch, F32 *yaw) const fn + using Catch::Matchers::WithinAbs; + + // The identity has no angles, whatever the outputs held before. F32 roll = -12.0f; F32 pitch = -22.43f; F32 yaw = 11.0f; - LLQuaternion llquat; llquat.getEulerAngles(&roll, &pitch, &yaw); - CHECK(is_approx_equal(0.000f, llquat.mQ[0])); - CHECK(is_approx_equal(0.000f, llquat.mQ[1])); - CHECK(is_approx_equal(0.000f, llquat.mQ[2])); - CHECK(is_approx_equal(1.000f, llquat.mQ[3])); + CHECK_THAT(roll, WithinAbs(0.f, 1e-6)); + CHECK_THAT(pitch, WithinAbs(0.f, 1e-6)); + CHECK_THAT(yaw, WithinAbs(0.f, 1e-6)); + + // Angles within range, pitch short of a quarter turn, round trip. + const F32 angles[][3] = { { 0.3f, -0.2f, 1.1f }, { -2.5f, 1.2f, -0.4f }, { 1.f, 0.f, 0.f } }; + for (const auto& set : angles) + { + CAPTURE(set[0], set[1], set[2]); + LLQuaternion turned; + turned.setEulerAngles(set[0], set[1], set[2]); + turned.getEulerAngles(&roll, &pitch, &yaw); + CHECK_THAT(roll, WithinAbs(set[0], 1e-5)); + CHECK_THAT(pitch, WithinAbs(set[1], 1e-5)); + CHECK_THAT(yaw, WithinAbs(set[2], 1e-5)); + } } TEST_CASE("LLQuaternion: product and dot evaluate at compile time", "[LLQuaternion]") diff --git a/indra/llmath/tests/llrect_test.cpp b/indra/llmath/tests/llrect_test.cpp index 72a8e22f5d..d4cd29e487 100644 --- a/indra/llmath/tests/llrect_test.cpp +++ b/indra/llmath/tests/llrect_test.cpp @@ -311,8 +311,25 @@ namespace CHECK_FALSE(rect1 == rect2); CHECK(rect1 != rect2); - CHECK(rect1 == rect1); - CHECK_FALSE(rect1 != rect1); + // Another rect with the same edges, not the same rect. + LLRectf same; + same.set(-1.0f, 1.0f, 1.0f, -1.0f); + CHECK(rect1 == same); + CHECK_FALSE(rect1 != same); + + // Each edge counts. + const LLRectf apart[] = { + LLRectf(-1.5f, 1.0f, 1.0f, -1.0f), + LLRectf(-1.0f, 1.5f, 1.0f, -1.0f), + LLRectf(-1.0f, 1.0f, 1.5f, -1.0f), + LLRectf(-1.0f, 1.0f, 1.0f, -1.5f), + }; + for (const LLRectf& other : apart) + { + CAPTURE(other); + CHECK(rect1 != other); + CHECK_FALSE(rect1 == other); + } } TEST_CASE("LLRect: copy constructor gives an equal rect", "[LLRect]") @@ -478,7 +495,10 @@ namespace LLRectf rect(1.0f, 3.0f, 3.0f, 1.0f); CHECK(rect.localPointInRect(0.0f, 0.0f)); - CHECK_FALSE(rect.localPointInRect(-0.0001f, -0.001f)); + CHECK_FALSE(rect.localPointInRect(-0.0001f, -0.0001f)); + // Just outside on one side is outside. + CHECK_FALSE(rect.localPointInRect(-0.0001f, 0.0f)); + CHECK_FALSE(rect.localPointInRect(0.0f, -0.0001f)); CHECK(rect.localPointInRect(1.0f, 1.0f)); CHECK_FALSE(rect.localPointInRect(2.0f, 2.0f)); CHECK(rect.localPointInRect(1.999f, 1.999f)); diff --git a/indra/llmath/tests/llsdutil_math_test.cpp b/indra/llmath/tests/llsdutil_math_test.cpp index 0314c32592..afe8efb194 100644 --- a/indra/llmath/tests/llsdutil_math_test.cpp +++ b/indra/llmath/tests/llsdutil_math_test.cpp @@ -31,54 +31,17 @@ #include "altestmatchers.h" #include "m4math.h" #include "v2math.h" -#include "v2math.h" #include "v3color.h" #include "v3math.h" #include "v3dmath.h" #include "v4coloru.h" #include "v4math.h" #include "llquaternion.h" -#include "llsdutil.h" #include "llsdutil_math.h" -#include "stringize.h" -#include -#include namespace { - struct llsdutil_math_data - { - void test_matches(const std::string& proto_key, const LLSD& possibles, - const char** begin, const char** end) - { - std::set succeed(begin, end); - LLSD prototype(possibles[proto_key]); - for (LLSD::map_const_iterator pi(possibles.beginMap()), pend(possibles.endMap()); - pi != pend; ++pi) - { - std::string match(llsd_matches(prototype, pi->second)); - std::set::const_iterator found = succeed.find(pi->first); - if (found != succeed.end()) - { - // This test is supposed to succeed. Comparing to the - // empty string ensures that if the test fails, it will - // display the string received so we can tell what failed. - REQUIRE(match == ""); - } - else - { - // This test is supposed to fail. If we get a false match, - // the string 'match' will be empty, which doesn't tell us - // much about which case went awry. So construct a more - // detailed description string. - INFO(proto_key << " shouldn't match " << pi->first); - REQUIRE_FALSE(match.empty()); - } - } - } - }; - - TEST_CASE_METHOD(llsdutil_math_data, "llsdutil_math: LLVector3 round-trips through LLSD", "[llsdutil_math]") + TEST_CASE("llsdutil_math: LLVector3 round-trips through LLSD", "[llsdutil_math]") { LLSD sd; LLVector3 vec1(-1.0, 2.0, -3.0); @@ -97,7 +60,7 @@ namespace sd.clear(); } - TEST_CASE_METHOD(llsdutil_math_data, "llsdutil_math: LLVector3d round-trips through LLSD", "[llsdutil_math]") + TEST_CASE("llsdutil_math: LLVector3d round-trips through LLSD", "[llsdutil_math]") { LLSD sd; LLVector3d vec1((F64)(U64L(0xFEDCBA9876543210) << 2), -1., 0); @@ -109,7 +72,7 @@ namespace CHECK(vec1 == vec3); } - TEST_CASE_METHOD(llsdutil_math_data, "llsdutil_math: LLVector2 and its LLSD round-trip both ways", "[llsdutil_math]") + TEST_CASE("llsdutil_math: LLVector2 and its LLSD round-trip both ways", "[llsdutil_math]") { LLSD sd; LLVector2 vec((F32) -3., (F32) 4.2); @@ -121,7 +84,7 @@ namespace CHECK_THAT(sd, ll_test::LLSDEquals(sd2)); } - TEST_CASE_METHOD(llsdutil_math_data, "llsdutil_math: LLQuaternion and its LLSD round-trip both ways", "[llsdutil_math]") + TEST_CASE("llsdutil_math: LLQuaternion and its LLSD round-trip both ways", "[llsdutil_math]") { LLSD sd; LLQuaternion quat((F32) 1., (F32) -0.98, (F32) 2.3, (F32) 0xffff); @@ -133,7 +96,7 @@ namespace CHECK_THAT(sd, ll_test::LLSDEquals(sd2)); } - TEST_CASE_METHOD(llsdutil_math_data, "llsdutil_math: LLColor4 and its LLSD round-trip both ways", "[llsdutil_math]") + TEST_CASE("llsdutil_math: LLColor4 and its LLSD round-trip both ways", "[llsdutil_math]") { LLSD sd; LLColor4 c(1.0f, 2.2f, 4.0f, 7.f); diff --git a/indra/llmath/tests/m3math_test.cpp b/indra/llmath/tests/m3math_test.cpp index 35b6ba611c..fe22e51043 100644 --- a/indra/llmath/tests/m3math_test.cpp +++ b/indra/llmath/tests/m3math_test.cpp @@ -42,11 +42,6 @@ #pragma warning(disable: 4702) #endif -#if LL_WINDOWS -// disable unreachable code warnings caused by usage of skip. -#pragma warning(disable: 4702) -#endif - namespace { //test case for setIdentity() fn. diff --git a/indra/llmath/tests/mathmisc_test.cpp b/indra/llmath/tests/mathmisc_test.cpp index 70dd912dee..407f77fe31 100644 --- a/indra/llmath/tests/mathmisc_test.cpp +++ b/indra/llmath/tests/mathmisc_test.cpp @@ -37,6 +37,7 @@ #include "../llline.h" #include "../llmath.h" #include "../v3math.h" +#include "../v3dmath.h" #include @@ -235,7 +236,8 @@ namespace struct line_data { - // The intersects and two-planes cases seed the main thread's generator. + // The intersects, nearest-approach and two-planes cases seed the main + // thread's generator. ~line_data() { ll_test::reseedRandomFromEntropy(); } }; @@ -303,23 +305,38 @@ namespace } } - TEST_CASE_METHOD(line_data, "LLLine: nearestApproach is disabled for DEV-16600", "[LLLine]") + TEST_CASE_METHOD(line_data, "LLLine: nearestApproach finds each line's point nearest the other", "[LLLine]") { - /* - These tests fail intermittently on all platforms - see DEV-16600 - Commenting this out until dev has time to investigate. - - // this is a test for LLLine::nearestApproach(LLLIne) method + // this is a test for LLLine::nearestApproach(LLLine) method // which computes the point on a line nearest another line - // these tests will have some floating point error, - // so we need to specify how much error is ok - // TODO -- make nearestApproach() algorithm more accurate so - // we can tighten the allowable_error. Most tests are tighter - // than one milimeter, however when doing randomized testing - // you can walk into inaccurate cases. + // Two lines whose nearest points are known exactly: the x axis, and + // a line along y through (3, 0, 5). + LLLine x_axis(LLVector3(0.f, 0.f, 0.f), LLVector3(1.f, 0.f, 0.f)); + LLLine above(LLVector3(3.f, -1.f, 5.f), LLVector3(3.f, 1.f, 5.f)); + CHECK(dist_vec(x_axis.nearestApproach(above), LLVector3(3.f, 0.f, 0.f)) < 1e-5f); + CHECK(dist_vec(above.nearestApproach(x_axis), LLVector3(3.f, 0.f, 5.f)) < 1e-5f); + + // Random lines, held to the nearest points worked out in double from + // the lines as they were made. Not to the points they were built + // through: making a line in float moves it, and where the two lines + // are near parallel that moves their nearest points by far more than + // nearestApproach's own error. That, not nearestApproach, was the + // intermittent failure DEV-16600 commented this test out for. + auto nearest_in_double = [](const LLLine& line, const LLLine& other) + { + const LLVector3d p1(line.getPoint()), d1(line.getDirection()); + const LLVector3d p2(other.getPoint()), d2(other.getDirection()); + const LLVector3d between = p1 - p2; + const F64 aa = d1 * d1, ab = d1 * d2, bb = d2 * d2, a_between = d1 * between, b_between = d2 * between; + const F64 along = (ab * b_between - bb * a_between) / (aa * bb - ab * ab); + return LLVector3(p1 + d1 * along); + }; + F32 allowable_relative_error = 0.001f; S32 number_of_tests = 100; + // A fixed seed, so a failure draws the same lines when run again. + ll_rand_seed(16600); for (S32 test = 0; test < number_of_tests; ++test) { // generate two points to be our known nearest approaches @@ -375,6 +392,10 @@ namespace LLVector3 some_computed_point = first_line.nearestApproach(second_line); LLVector3 another_computed_point = second_line.nearestApproach(first_line); + // what they should be, for the lines as made + some_point = nearest_in_double(first_line, second_line); + another_point = nearest_in_double(second_line, first_line); + // compute the error F32 first_error = (some_point - some_computed_point).length(); F32 scale = llmax((some_point - another_point).length(), some_point.length()); @@ -385,32 +406,11 @@ namespace F32 second_error = (another_point - another_computed_point).length(); F32 second_relative_error = second_error / scale; - //if (first_relative_error > allowable_relative_error) - //{ - // std::cout << "first_error = " << first_error - // << " first_relative_error = " << first_relative_error - // << " scale = " << scale - // << " dir_dot = " << (first_dir * second_dir) - // << std::endl; - //} - //if (second_relative_error > allowable_relative_error) - //{ - // std::cout << "second_error = " << second_error - // << " second_relative_error = " << second_relative_error - // << " scale = " << scale - // << " dist = " << (some_point - another_point).length() - // << " dir_dot = " << (first_dir * second_dir) - // << std::endl; - //} - // test that the errors are small - - ensure("first line should accurately compute its closest approach", - first_relative_error <= allowable_relative_error); - ensure("second line should accurately compute its closest approach", - second_relative_error <= allowable_relative_error); + CAPTURE(test, scale, first_dir * second_dir); + REQUIRE(first_relative_error <= allowable_relative_error); + REQUIRE(second_relative_error <= allowable_relative_error); } - */ } F32 ALMOST_PARALLEL = 0.99f; diff --git a/indra/llmath/tests/v3color_test.cpp b/indra/llmath/tests/v3color_test.cpp index 7ffa6cb49a..906f3a4bd9 100644 --- a/indra/llmath/tests/v3color_test.cpp +++ b/indra/llmath/tests/v3color_test.cpp @@ -236,9 +236,13 @@ namespace CHECK(is_approx_equal(r1+r2 ,llcolor3a.mV[0])); CHECK(is_approx_equal(g1+g2,llcolor3a.mV[1])); CHECK(is_approx_equal(b1+b2,llcolor3a.mV[2])); + + // Again on other values, and it hands back the color it changed. + r1 = -.235f, g1 = -24.32f, b1 = 2.13f, r2 = .5f, g2 = 1.f, b2 = -34.21f; llcolor3.setVec(r1,g1,b1); llcolor3a.setVec(r2,g2,b2); - llcolor3a += llcolor3; + const LLColor3& sum = (llcolor3a += llcolor3); + CHECK(&sum == &llcolor3a); CHECK(is_approx_equal(r1+r2 ,llcolor3a.mV[0])); CHECK(is_approx_equal(g1+g2,llcolor3a.mV[1])); CHECK(is_approx_equal(b1+b2,llcolor3a.mV[2])); @@ -252,9 +256,13 @@ namespace CHECK(is_approx_equal(r2-r1, llcolor3a.mV[0])); CHECK(is_approx_equal(g2-g1, llcolor3a.mV[1])); CHECK(is_approx_equal(b2-b1, llcolor3a.mV[2])); + + // Again on other values, and it hands back the color it changed. + r1 = -.235f, g1 = -24.32f, b1 = 2.13f, r2 = .5f, g2 = 1.f, b2 = -34.21f; llcolor3.setVec(r1,g1,b1); llcolor3a.setVec(r2,g2,b2); - llcolor3a -= llcolor3; + const LLColor3& difference = (llcolor3a -= llcolor3); + CHECK(&difference == &llcolor3a); CHECK(is_approx_equal(r2-r1, llcolor3a.mV[0])); CHECK(is_approx_equal(g2-g1, llcolor3a.mV[1])); CHECK(is_approx_equal(b2-b1, llcolor3a.mV[2])); diff --git a/indra/llmath/tests/v3dmath_test.cpp b/indra/llmath/tests/v3dmath_test.cpp index 3f660402ec..58f62a29a8 100644 --- a/indra/llmath/tests/v3dmath_test.cpp +++ b/indra/llmath/tests/v3dmath_test.cpp @@ -46,28 +46,38 @@ namespace CHECK(vec3D.mdV[VY] == 0); CHECK(vec3D.mdV[VZ] == 0); - F64 x = 2.32f, y = 1.212f, z = -.12f; + F64 x = 2.32, y = 1.212, z = -.12; LLVector3d vec3Da(x,y,z); - CHECK(vec3Da.mdV[VX] == 2.32f); - CHECK(vec3Da.mdV[VY] == 1.212f); - CHECK(vec3Da.mdV[VZ] == -.12f); + CHECK(vec3Da.mdV[VX] == 2.32); + CHECK(vec3Da.mdV[VY] == 1.212); + CHECK(vec3Da.mdV[VZ] == -.12); - const F64 vec[3] = {1.2f ,3.2f, -4.2f}; + const F64 vec[3] = {1.2 ,3.2, -4.2}; LLVector3d vec3Db(vec); - CHECK(vec3Db.mdV[VX] == 1.2f); - CHECK(vec3Db.mdV[VY] == 3.2f); - CHECK(vec3Db.mdV[VZ] == -4.2f); + CHECK(vec3Db.mdV[VX] == 1.2); + CHECK(vec3Db.mdV[VY] == 3.2); + CHECK(vec3Db.mdV[VZ] == -4.2); - LLVector3 vec3((F32)x,(F32)y,(F32)z); + // From an LLVector3, each float widened exactly. + LLVector3 vec3(2.32f, 1.212f, -.12f); LLVector3d vec3Dc(vec3); - CHECK(vec3Da == vec3Dc); + CHECK(vec3Dc.mdV[VX] == F64(2.32f)); + CHECK(vec3Dc.mdV[VY] == F64(1.212f)); + CHECK(vec3Dc.mdV[VZ] == F64(-.12f)); } TEST_CASE("v3dmath_h: LLSD constructor and getValue round trip", "[v3dmath_h]") { - S32 a = -235; - LLSD llsd(a); + // An array of three: a vector reads sd[0], sd[1] and sd[2], so a + // scalar LLSD would make every component zero. + LLSD llsd; + llsd[0] = -235.0; + llsd[1] = 232345521.411132; + llsd[2] = -.12; LLVector3d vec3d(llsd); + CHECK(vec3d.mdV[VX] == -235.0); + CHECK(vec3d.mdV[VY] == 232345521.411132); + CHECK(vec3d.mdV[VZ] == -.12); LLSD sd = vec3d.getValue(); LLVector3d vec3da(sd); CHECK(vec3d == vec3da); @@ -75,12 +85,18 @@ namespace TEST_CASE("v3dmath_h: setValue and getValue round trip", "[v3dmath_h]") { - F64 a = 232345521.411132; - LLSD llsd(a); + LLSD llsd; + llsd[0] = 232345521.411132; + llsd[1] = -0.000001; + llsd[2] = 12345.2343; LLVector3d vec3d; vec3d.setValue(llsd); + CHECK(vec3d.mdV[VX] == 232345521.411132); + CHECK(vec3d.mdV[VY] == -0.000001); + CHECK(vec3d.mdV[VZ] == 12345.2343); LLSD sd = vec3d.getValue(); - LLVector3d vec3da(sd); + LLVector3d vec3da; + vec3da.setValue(sd); CHECK(vec3d == vec3da); } @@ -100,6 +116,7 @@ namespace TEST_CASE("v3dmath_h: clearVec/zeroVec and the setVec overloads", "[v3dmath_h]") { + // Values a float holds, so the trip through an LLVector3 below is exact. F64 x = 2.32f, y = 1.212f, z = -.12f; LLVector3d vec3D(x,y,z); vec3D.clearVec(); @@ -164,15 +181,15 @@ namespace CHECK(x == vec3D[0]); CHECK(y == vec3D[1]); CHECK(z == vec3D[2]); - vec3D.clearVec(); - x = 23.23, y = -.2361, z = 3.25; - vec3D.setVec(x,y,z); + // The non-const subscript is a reference to the component itself. F64 &ref1 = vec3D[0]; - CHECK(ref1 == vec3D[0]); - F64 &ref2 = vec3D[1]; - CHECK(ref2 == vec3D[1]); - F64 &ref3 = vec3D[2]; - CHECK(ref3 == vec3D[2]); + CHECK(&ref1 == &vec3D.mdV[VX]); + ref1 = 23.23; + vec3D[1] = -.2361; + vec3D[2] = 3.25; + CHECK(vec3D.mdV[VX] == 23.23); + CHECK(vec3D.mdV[VY] == -.2361); + CHECK(vec3D.mdV[VZ] == 3.25); } TEST_CASE("v3dmath_h: assignment from LLVector4", "[v3dmath_h]") diff --git a/indra/llmath/tests/v3math_test.cpp b/indra/llmath/tests/v3math_test.cpp index 18ded63c88..b5368bdc0e 100644 --- a/indra/llmath/tests/v3math_test.cpp +++ b/indra/llmath/tests/v3math_test.cpp @@ -28,6 +28,7 @@ #include "linden_common.h" #include +#include #include "llsd.h" #include "../v3dmath.h" @@ -74,9 +75,16 @@ namespace TEST_CASE("v3math_h: LLSD constructor and getValue round trip", "[v3math_h]") { - S32 a = 231; - LLSD llsd(a); + // An array of three: a vector reads sd[0], sd[1] and sd[2], so a + // scalar LLSD would make every component zero. + LLSD llsd; + llsd[0] = 2.32; + llsd[1] = 1.212; + llsd[2] = -.12; LLVector3 vec3(llsd); + CHECK(vec3.mV[VX] == 2.32f); + CHECK(vec3.mV[VY] == 1.212f); + CHECK(vec3.mV[VZ] == -.12f); LLSD sd = vec3.getValue(); LLVector3 vec3a(sd); CHECK(vec3 == vec3a); @@ -84,11 +92,11 @@ namespace TEST_CASE("v3math_h: assignment copies every component", "[v3math_h]") { - S32 a = 231; - LLSD llsd(a); - LLVector3 vec3(llsd),vec3a; + LLVector3 vec3(2.32f, 1.212f, -.12f), vec3a; vec3a = vec3; - CHECK(vec3 == vec3a); + CHECK(vec3a.mV[VX] == 2.32f); + CHECK(vec3a.mV[VY] == 1.212f); + CHECK(vec3a.mV[VZ] == -.12f); } TEST_CASE("v3math_h: isFinite and the clearVec/setVec/zeroVec setters", "[v3math_h]") @@ -193,15 +201,43 @@ namespace CHECK_FALSE(vec3.isExactlyZero()); } - TEST_CASE("v3math_h: rotVec by an angle about an axis is repeatable", "[v3math_h]") - { - F32 x =20.0f, y = 30.0f, z = 15.f ; - F32 angle = 100.f; - LLVector3 vec3(x,y,z),vec3a(1.f,2.f,3.f); - vec3a = vec3a.rotVec(angle, vec3); - LLVector3 vec3b(1.f,2.f,3.f); - vec3b = vec3b.rotVec(angle, vec3); - CHECK(vec3b == vec3a); + TEST_CASE("v3math_h: rotVec turns a vector by an angle about an axis", "[v3math_h]") + { + using Catch::Matchers::WithinAbs; + + // A quarter turn about z takes x to y. + LLVector3 quarter(1.f, 0.f, 0.f); + quarter.rotVec(F_PI_BY_TWO, LLVector3(0.f, 0.f, 1.f)); + CHECK_THAT(quarter.mV[VX], WithinAbs(0.f, 1e-6)); + CHECK_THAT(quarter.mV[VY], WithinAbs(1.f, 1e-6)); + CHECK_THAT(quarter.mV[VZ], WithinAbs(0.f, 1e-6)); + + // Any angle about any axis, against Rodrigues' formula in double: + // v cos a + (k x v) sin a + k (k . v)(1 - cos a), k the unit axis. + const F64 angle = 100.0; + const F64 axis_length = sqrt(20.0 * 20.0 + 30.0 * 30.0 + 15.0 * 15.0); + const F64 k[3] = { 20.0 / axis_length, 30.0 / axis_length, 15.0 / axis_length }; + const F64 v[3] = { 1.0, 2.0, 3.0 }; + const F64 k_dot_v = k[0] * v[0] + k[1] * v[1] + k[2] * v[2]; + const F64 k_cross_v[3] = { k[1] * v[2] - k[2] * v[1], k[2] * v[0] - k[0] * v[2], k[0] * v[1] - k[1] * v[0] }; + F64 expected[3]; + for (int i = 0; i < 3; ++i) + { + expected[i] = v[i] * cos(angle) + k_cross_v[i] * sin(angle) + k[i] * k_dot_v * (1.0 - cos(angle)); + } + + LLVector3 vec3((F32)v[0], (F32)v[1], (F32)v[2]); + vec3.rotVec((F32)angle, LLVector3(20.f, 30.f, 15.f)); + CHECK_THAT(vec3.mV[VX], WithinAbs(expected[0], 1e-4)); + CHECK_THAT(vec3.mV[VY], WithinAbs(expected[1], 1e-4)); + CHECK_THAT(vec3.mV[VZ], WithinAbs(expected[2], 1e-4)); + + // No turn about a zero axis, or by a zero angle. + LLVector3 still(1.f, 2.f, 3.f); + still.rotVec((F32)angle, LLVector3::zero); + CHECK(still == LLVector3(1.f, 2.f, 3.f)); + still.rotVec(0.f, LLVector3(20.f, 30.f, 15.f)); + CHECK(still == LLVector3(1.f, 2.f, 3.f)); } TEST_CASE("v3math_h: subscript operator reads components and returns references", "[v3math_h]") @@ -212,15 +248,15 @@ namespace CHECK(y == vec3[1]); CHECK(z == vec3[2]); - vec3.clearVec(); - x = 23.f, y = -.2361f, z = 3.25; - vec3.setVec(x,y,z); + // The non-const subscript is a reference to the component itself. F32 &ref1 = vec3[0]; - CHECK(ref1 == vec3[0]); - F32 &ref2 = vec3[1]; - CHECK(ref2 == vec3[1]); - F32 &ref3 = vec3[2]; - CHECK(ref3 == vec3[2]); + CHECK(&ref1 == &vec3.mV[VX]); + ref1 = 23.f; + vec3[1] = -.2361f; + vec3[2] = 3.25f; + CHECK(vec3.mV[VX] == 23.f); + CHECK(vec3.mV[VY] == -.2361f); + CHECK(vec3.mV[VZ] == 3.25f); } TEST_CASE("v3math_h: operator+ adds componentwise", "[v3math_h]") @@ -376,19 +412,21 @@ namespace TEST_CASE("v3math_h: operator!= for different and equal vectors", "[v3math_h]") { - F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, x2 =112.f, y2 = 2.234f,z2 = 11.2f;; + F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, x2 =112.f, y2 = 2.234f,z2 = 11.2f; LLVector3 vec3(x1,y1,z1), vec3a(x2,y2,z2); CHECK(vec3a != vec3); - vec3.clearVec(); - vec3.clearVec(); + // One component apart is enough. + vec3a.setVec(x1, y1, z2); + CHECK(vec3a != vec3); + vec3a.setVec(vec3); CHECK_FALSE(vec3a != vec3); } TEST_CASE("v3math_h: operator+= adds componentwise", "[v3math_h]") { - F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, x2 =112.f, y2 = 2.2f,z2 = 11.2f;; + F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, x2 =112.f, y2 = 2.2f,z2 = 11.2f; LLVector3 vec3(x1,y1,z1), vec3a(x2,y2,z2); vec3a += vec3; F32 val1, val2, val3; @@ -402,7 +440,7 @@ namespace TEST_CASE("v3math_h: operator-= subtracts componentwise", "[v3math_h]") { - F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, x2 =112.f, y2 = 2.2f,z2 = 11.2f;; + F32 x1 =1.f, y1 = 2.f,z1 = 1.2f, x2 =112.f, y2 = 2.2f,z2 = 11.2f; LLVector3 vec3(x1,y1,z1), vec3a(x2,y2,z2); vec3a -= vec3; F32 val1, val2, val3; @@ -479,19 +517,19 @@ namespace CHECK(stream1.str() == stream2.str()); } - TEST_CASE("v3math_h: operator< orders vectors by x first", "[v3math_h]") + TEST_CASE("v3math_h: operator< orders by x, then y, then z", "[v3math_h]") { - F32 x1 =-2.3f, y1 = 2.f,z1 = 1.2f, x2 = 1.3f, y2 = 1.11f, z2 = 1234.234f; - LLVector3 vec3(x1,y1,z1), vec3a(x2,y2,z2); - CHECK(vec3 < vec3a); - x1 =-2.3f, y1 = 2.f,z1 = 1.2f, x2 = 1.3f, y2 = 2.f, z2 = 1234.234f; - vec3.setVec(x1,y1,z1); - vec3a.setVec(x2,y2,z2); - CHECK(vec3 < vec3a); - x1 =2.3f, y1 = 2.f,z1 = 1.2f, x2 = 1.3f, - vec3.setVec(x1,y1,z1); - vec3a.setVec(x2,y2,z2); - CHECK_FALSE(vec3 < vec3a); + // x decides wherever the two differ in it, whatever y and z say. + CHECK(LLVector3(-2.3f, 2.f, 1.2f) < LLVector3(1.3f, 1.11f, 1234.234f)); + CHECK_FALSE(LLVector3(2.3f, 2.f, 1.2f) < LLVector3(1.3f, 2.f, 1234.234f)); + // Equal in x, y decides. + CHECK(LLVector3(1.f, 2.f, 9.f) < LLVector3(1.f, 3.f, 0.f)); + CHECK_FALSE(LLVector3(1.f, 3.f, 0.f) < LLVector3(1.f, 2.f, 9.f)); + // Equal in x and y, z decides. + CHECK(LLVector3(1.f, 2.f, 3.f) < LLVector3(1.f, 2.f, 4.f)); + CHECK_FALSE(LLVector3(1.f, 2.f, 4.f) < LLVector3(1.f, 2.f, 3.f)); + // Strict: no vector is less than itself. + CHECK_FALSE(LLVector3(1.f, 2.f, 3.f) < LLVector3(1.f, 2.f, 3.f)); } TEST_CASE("v3math_h: parseVector3 reads three numbers", "[v3math_h]") diff --git a/indra/llmath/tests/v4color_test.cpp b/indra/llmath/tests/v4color_test.cpp index 3f72e62c75..759f396ef1 100644 --- a/indra/llmath/tests/v4color_test.cpp +++ b/indra/llmath/tests/v4color_test.cpp @@ -218,14 +218,15 @@ namespace CHECK(g == llcolor4[1]); CHECK(b == llcolor4[2]); - r = 0xA20, g = 0xFBFF, b = 0xFFF; - llcolor4.setVec(r,g,b); + // The non-const subscript is a reference to the component itself. F32 &ref1 = llcolor4[0]; - CHECK(ref1 == llcolor4[0]); - F32 &ref2 = llcolor4[1]; - CHECK(ref2 == llcolor4[1]); - F32 &ref3 = llcolor4[2]; - CHECK(ref3 == llcolor4[2]); + CHECK(&ref1 == &llcolor4.mV[VRED]); + ref1 = 0xA20; + llcolor4[1] = 0xFBFF; + llcolor4[2] = 0xFFF; + CHECK(llcolor4.mV[VRED] == 0xA20); + CHECK(llcolor4.mV[VGREEN] == 0xFBFF); + CHECK(llcolor4.mV[VBLUE] == 0xFFF); } TEST_CASE("v4color_h: assignment from LLColor3 and construction from LLSD", "[v4color_h]") diff --git a/indra/llmath/tests/v4math_test.cpp b/indra/llmath/tests/v4math_test.cpp index ac96ff8515..f3d1e10526 100644 --- a/indra/llmath/tests/v4math_test.cpp +++ b/indra/llmath/tests/v4math_test.cpp @@ -28,6 +28,7 @@ #include "linden_common.h" #include +#include #include "altestmatchers.h" #include "llsd.h" @@ -182,20 +183,24 @@ namespace CHECK(y == vec4[1]); CHECK(z == vec4[2]); CHECK(w == vec4[3]); - x = 23.f, y = -.2361f, z = 3.25; - vec4.setVec(x,y,z); + // The non-const subscript is a reference to the component itself. F32 &ref1 = vec4[0]; - CHECK(ref1 == vec4[0]); - F32 &ref2 = vec4[1]; - CHECK(ref2 == vec4[1]); - F32 &ref3 = vec4[2]; - CHECK(ref3 == vec4[2]); - F32 &ref4 = vec4[3]; - CHECK(ref4 == vec4[3]); + CHECK(&ref1 == &vec4.mV[VX]); + ref1 = 23.f; + vec4[1] = -.2361f; + vec4[2] = 3.25f; + vec4[3] = 7.5f; + CHECK(vec4.mV[VX] == 23.f); + CHECK(vec4.mV[VY] == -.2361f); + CHECK(vec4.mV[VZ] == 3.25f); + CHECK(vec4.mV[VW] == 7.5f); } - TEST_CASE("v4math_h: rotVec by a matrix and by a quaternion is repeatable", "[v4math_h]") + TEST_CASE("v4math_h: rotVec by a matrix and by a quaternion", "[v4math_h]") { + using Catch::Matchers::WithinAbs; + + // By a matrix: the row vector times the matrix, all four components. F32 x = 10.f, y = -2.3f, z = -.023f, w = -2.0f; const F32 val[16] = { 1.f, 2.f, 3.f, 0.f, @@ -204,18 +209,37 @@ namespace .89f, 0.f, 0.f, 0.f }; LLMatrix4 mat(val); - LLVector4 vec4(x,y,z,w),vec4a; + LLVector4 vec4(x,y,z,w); vec4.rotVec(mat); - vec4a.setVec(x,y,z,w); - vec4a.rotVec(mat); - CHECK(vec4a == vec4); - F32 a = 2.32f, b = -23.2f, c = -34.1112f, d = 1.010112f; - LLQuaternion q(a,b,c,d); - LLVector4 vec4b(a,b,c,d),vec4c; + const F64 v[4] = { x, y, z, w }; + for (int col = 0; col < 4; ++col) + { + F64 expected = 0.0; + for (int row = 0; row < 4; ++row) + { + expected += v[row] * val[row * 4 + col]; + } + CAPTURE(col); + CHECK_THAT(vec4.mV[col], WithinAbs(expected, 1e-4)); + } + + // By a quaternion: x, y and z turn as an LLVector3 does, and w stays. + LLVector4 quarter(1.f, 0.f, 0.f, 3.f); + quarter.rotVec(LLQuaternion(F_PI_BY_TWO, LLVector3(0.f, 0.f, 1.f))); + CHECK_THAT(quarter.mV[VX], WithinAbs(0.f, 1e-6)); + CHECK_THAT(quarter.mV[VY], WithinAbs(1.f, 1e-6)); + CHECK_THAT(quarter.mV[VZ], WithinAbs(0.f, 1e-6)); + CHECK(quarter.mV[VW] == 3.f); + + // A vector off the turn's axis, which the turn moves. + const LLQuaternion q(1.3f, LLVector3(2.32f, -23.2f, -34.1112f)); + LLVector4 vec4b(1.f, 2.f, 3.f, 1.010112f); vec4b.rotVec(q); - vec4c.setVec(a, b, c, d); - vec4c.rotVec(q); - CHECK(vec4b == vec4c); + const LLVector3 turned = LLVector3(1.f, 2.f, 3.f) * q; + CHECK(vec4b.mV[VX] == turned.mV[VX]); + CHECK(vec4b.mV[VY] == turned.mV[VY]); + CHECK(vec4b.mV[VZ] == turned.mV[VZ]); + CHECK(vec4b.mV[VW] == 1.010112f); } TEST_CASE("v4math_h: operator<< prints equal vectors the same", "[v4math_h]") diff --git a/indra/llmath/tests/xform_test.cpp b/indra/llmath/tests/xform_test.cpp index bfda263550..ff78bf24ef 100644 --- a/indra/llmath/tests/xform_test.cpp +++ b/indra/llmath/tests/xform_test.cpp @@ -140,20 +140,34 @@ namespace xform_obj.clearChanged(LLXform::TRANSLATED | LLXform::ROTATED | LLXform::SCALED); CHECK_FALSE(xform_obj.isChanged()); + // isChanged(bits) is true when any of the bits is set, so each flag + // is asked about on its own. LLVector3 llvect3(12.4f, -5.6f, 0.34f); xform_obj.setScale(llvect3); CHECK(xform_obj.isChanged(LLXform::SCALED)); + CHECK_FALSE(xform_obj.isChanged(LLXform::TRANSLATED)); + CHECK_FALSE(xform_obj.isChanged(LLXform::ROTATED)); + xform_obj.setPosition(1.2f, 2.3f, 3.4f); CHECK(xform_obj.isChanged(LLXform::TRANSLATED)); UNSCOPED_INFO("setPosition must not clear SCALED"); - CHECK(xform_obj.isChanged(LLXform::TRANSLATED | LLXform::SCALED)); + CHECK(xform_obj.isChanged(LLXform::SCALED)); + CHECK_FALSE(xform_obj.isChanged(LLXform::ROTATED)); + xform_obj.clearChanged(LLXform::SCALED); CHECK_FALSE(xform_obj.isChanged(LLXform::SCALED)); + UNSCOPED_INFO("clearing SCALED must leave TRANSLATED"); + CHECK(xform_obj.isChanged(LLXform::TRANSLATED)); + xform_obj.setRotation(1, 2, 3, 4); - UNSCOPED_INFO("setRotation must set ROTATED"); - CHECK(xform_obj.isChanged(LLXform::TRANSLATED | LLXform::ROTATED)); + CHECK(xform_obj.isChanged(LLXform::ROTATED)); + CHECK_FALSE(xform_obj.isChanged(LLXform::SCALED)); + + // MOVED is the three together. xform_obj.setScale(llvect3); - CHECK(xform_obj.isChanged(LLXform::MOVED)); + CHECK(xform_obj.isChanged(LLXform::SCALED)); + xform_obj.clearChanged(LLXform::MOVED); + CHECK_FALSE(xform_obj.isChanged()); } //to test init() and getWorldMatrix() fns. @@ -182,19 +196,20 @@ namespace } //to test mMin.clearVec() and mMax.clearVec() fns - TEST_CASE("LLXForm: getMinMax after init puts the minimum corner at the origin", "[LLXForm]") + TEST_CASE("LLXForm: getMinMax after init puts both corners at the origin", "[LLXForm]") { LLXformMatrix formMatrix_obj; formMatrix_obj.init(); - LLVector3 llmin_vec3; - LLVector3 llmax_vec3; + // Not zero to begin with, so the zeros below are getMinMax's. + LLVector3 llmin_vec3(1.f, 1.f, 1.f); + LLVector3 llmax_vec3(1.f, 1.f, 1.f); formMatrix_obj.getMinMax(llmin_vec3, llmax_vec3); CHECK(llmin_vec3.mV[0] == 0.f); CHECK(llmin_vec3.mV[1] == 0.f); CHECK(llmin_vec3.mV[2] == 0.f); - CHECK(llmin_vec3.mV[0] == 0.f); - CHECK(llmin_vec3.mV[1] == 0.f); - CHECK(llmin_vec3.mV[2] == 0.f); + CHECK(llmax_vec3.mV[0] == 0.f); + CHECK(llmax_vec3.mV[1] == 0.f); + CHECK(llmax_vec3.mV[2] == 0.f); } //test case of update() fn. diff --git a/indra/test/altestmatchers.cpp b/indra/test/altestmatchers.cpp index 633a7ce47a..b790f5754f 100644 --- a/indra/test/altestmatchers.cpp +++ b/indra/test/altestmatchers.cpp @@ -30,12 +30,22 @@ #include "llsdserialize.h" #include +#include #include namespace ll_test { namespace { + // A NaN equals nothing, itself included. Under /fp:fast, which the + // optimised Windows builds use, MSVC compiles == without its test for + // an unordered result, so a NaN would equal anything: it is asked + // about apart. + bool sameNumber(F64 actual, F64 expected) + { + return !std::isnan(actual) && !std::isnan(expected) && actual == expected; + } + // Where actual and expected first differ: a path from the top, as in // ["key"][3], and what differs there. Empty "what" means equal. struct Difference @@ -62,7 +72,7 @@ namespace ll_test same = actual.asInteger() == expected.asInteger(); break; case LLSD::TypeReal: - same = actual.asReal() == expected.asReal(); + same = sameNumber(actual.asReal(), expected.asReal()); break; case LLSD::TypeString: same = actual.asString() == expected.asString(); @@ -73,7 +83,7 @@ namespace ll_test case LLSD::TypeDate: // Not LLDate's ==, which is made of < and so holds a NaN // date equal to every date; TUT compared the seconds. - same = actual.asDate().secondsSinceEpoch() == expected.asDate().secondsSinceEpoch(); + same = sameNumber(actual.asDate().secondsSinceEpoch(), expected.asDate().secondsSinceEpoch()); break; case LLSD::TypeURI: same = actual.asURI().asString() == expected.asURI().asString(); diff --git a/indra/test/tests/altestsupport_test.cpp b/indra/test/tests/altestsupport_test.cpp index d8c1283ad7..d0b28927ac 100644 --- a/indra/test/tests/altestsupport_test.cpp +++ b/indra/test/tests/altestsupport_test.cpp @@ -101,6 +101,9 @@ TEST_CASE("LLSDEquals compares all the way down and says where two differ", "[al // LLDate's own == holds a NaN date equal to any. const LLSD nan_date = LLDate(std::numeric_limits::quiet_NaN()); CHECK_FALSE(ll_test::LLSDEquals(LLDate(0.0)).match(nan_date)); + // Nor a NaN number, which MSVC's /fp:fast compare would hold equal. + const LLSD nan_real = std::numeric_limits::quiet_NaN(); + CHECK_FALSE(ll_test::LLSDEquals(0.0).match(nan_real)); CHECK_THAT(LLSD(LLDate(1.0)), ll_test::LLSDEquals(LLDate(1.0))); }