You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

443 lines
17 KiB

/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include <new>
#include <sstream> /** @todo remove once Debug is STL-free */
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/Utility/DebugStl.h> /** @todo remove once Debug is STL-free */
#include "Magnum/Math/Time.h"
struct Time {
unsigned secondsSinceEpoch;
};
struct Keyframe {
float duration;
};
namespace Magnum { namespace Math {
namespace Implementation {
template<> struct NanosecondsConverter<Long, Time> {
constexpr static Nanoseconds<Long> from(Time other) {
return Nanoseconds<Long>{Long(other.secondsSinceEpoch)*1000000000ll};
}
constexpr static Time to(Nanoseconds<Long> other) {
return {unsigned(Long(other)/1000000000ll)};
}
};
template<> struct SecondsConverter<Float, Keyframe> {
constexpr static Seconds<Float> from(Keyframe other) {
return Seconds<Float>{other.duration};
}
constexpr static Keyframe to(Seconds<Float> other) {
return {Float(other)};
}
};
}
namespace Test { namespace {
struct TimeTest: TestSuite::Tester {
explicit TimeTest();
void limits();
void construct();
void constructDefault();
void constructNoInit();
void constructCopy();
void constructFromBase();
void convert();
void literals();
void conversion();
void nanosecondFloatScaling();
void debugNanoseconds();
void debugNanosecondsPacked();
void debugSeconds();
void debugSecondsPacked();
};
using Magnum::Nanoseconds;
using Magnum::Seconds;
using namespace Math::Literals;
TimeTest::TimeTest() {
addTests({&TimeTest::limits,
&TimeTest::construct,
&TimeTest::constructDefault,
&TimeTest::constructNoInit,
&TimeTest::constructCopy,
&TimeTest::constructFromBase,
&TimeTest::convert,
&TimeTest::literals,
&TimeTest::conversion,
&TimeTest::nanosecondFloatScaling,
&TimeTest::debugNanoseconds,
&TimeTest::debugNanosecondsPacked,
&TimeTest::debugSeconds,
&TimeTest::debugSecondsPacked});
}
void TimeTest::limits() {
/* There's apparently no way to say -0x8000000000000000ll, so there's also
no non-error-prone way to verify the values are correct. */
/* It should be all 64 bits (so 16 nibbles) being set */
CORRADE_COMPARE(UnsignedLong(Long(Nanoseconds::min()))|
UnsignedLong(Long(Nanoseconds::max())),
/* 0123456789abcdef */
0xffffffffffffffffull);
/* Assuming signed integer overflow is defined sanely, which it should */
CORRADE_COMPARE(Nanoseconds::min() - 1_nsec, Nanoseconds::max());
CORRADE_COMPARE(Nanoseconds::max() + 1_nsec, Nanoseconds::min());
/* This should also hold */
CORRADE_COMPARE(Nanoseconds::min() + Nanoseconds::max(), -1_nsec);
CORRADE_COMPARE(Nanoseconds::max() - Nanoseconds::min(), -1_nsec);
}
void TimeTest::construct() {
Nanoseconds a{-123456789123456789ll};
Seconds b{123.45f};
CORRADE_COMPARE(Long(a), -123456789123456789ll);
CORRADE_COMPARE(Float(b), 123.45f);
constexpr Nanoseconds ca{-123456789123456789ll};
constexpr Seconds cb{123.45f};
CORRADE_COMPARE(Long(ca), -123456789123456789ll);
CORRADE_COMPARE(Float(cb), 123.45f);
/* Implicit conversion is not allowed */
CORRADE_VERIFY(!std::is_convertible<Long, Nanoseconds>::value);
CORRADE_VERIFY(!std::is_convertible<Float, Seconds>::value);
CORRADE_VERIFY(std::is_nothrow_constructible<Nanoseconds, Long>::value);
CORRADE_VERIFY(std::is_nothrow_constructible<Seconds, Float>::value);
}
void TimeTest::constructDefault() {
Nanoseconds a1;
Nanoseconds a2{ZeroInit};
Seconds b1;
Seconds b2{ZeroInit};
CORRADE_COMPARE(Long(a1), 0ll);
CORRADE_COMPARE(Long(a2), 0ll);
CORRADE_COMPARE(Float(b1), 0.0f);
CORRADE_COMPARE(Float(b2), 0.0f);
constexpr Nanoseconds ca1;
constexpr Nanoseconds ca2{ZeroInit};
constexpr Seconds cb1;
constexpr Seconds cb2{ZeroInit};
CORRADE_COMPARE(Long(ca1), 0ll);
CORRADE_COMPARE(Long(ca2), 0ll);
CORRADE_COMPARE(Float(cb1), 0.0f);
CORRADE_COMPARE(Float(cb2), 0.0f);
CORRADE_VERIFY(std::is_nothrow_default_constructible<Nanoseconds>::value);
CORRADE_VERIFY(std::is_nothrow_default_constructible<Seconds>::value);
CORRADE_VERIFY(std::is_nothrow_constructible<Nanoseconds, ZeroInitT>::value);
CORRADE_VERIFY(std::is_nothrow_constructible<Seconds, ZeroInitT>::value);
/* Implicit construction is not allowed */
CORRADE_VERIFY(!std::is_convertible<ZeroInitT, Nanoseconds>::value);
CORRADE_VERIFY(!std::is_convertible<ZeroInitT, Seconds>::value);
}
void TimeTest::constructNoInit() {
Nanoseconds a{123456789123456789ll};
Seconds b{123.45f};
new(&a) Nanoseconds{Magnum::NoInit};
new(&b) Seconds{Magnum::NoInit};
{
/* Explicitly check we're not on Clang because certain Clang-based IDEs
inherit __GNUC__ if GCC is used instead of leaving it at 4 like
Clang itself does */
#if defined(CORRADE_TARGET_GCC) && !defined(CORRADE_TARGET_CLANG) && __GNUC__*100 + __GNUC_MINOR__ >= 601
/* The warning is reported for both debug and release build */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
/* On GCC 13 it's -Wuninitialized now, the compiler is now EVEN MORE
DEFINITELY RIGHT that I'm doing something wrong */
#if __GNUC__ >= 13
#pragma GCC diagnostic ignored "-Wuninitialized"
#endif
#ifdef __OPTIMIZE__
CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value.");
#endif
#endif
CORRADE_COMPARE(Long(a), 123456789123456789ll);
CORRADE_COMPARE(Float(b), 123.45f);
#if defined(CORRADE_TARGET_GCC) && !defined(CORRADE_TARGET_CLANG) && __GNUC__*100 + __GNUC_MINOR__ >= 601
#pragma GCC diagnostic pop
#endif
}
CORRADE_VERIFY(std::is_nothrow_constructible<Nanoseconds, Magnum::NoInitT>::value);
CORRADE_VERIFY(std::is_nothrow_constructible<Seconds, Magnum::NoInitT>::value);
/* Implicit construction is not allowed */
CORRADE_VERIFY(!std::is_convertible<Magnum::NoInitT, Nanoseconds>::value);
CORRADE_VERIFY(!std::is_convertible<Magnum::NoInitT, Seconds>::value);
}
void TimeTest::constructCopy() {
Nanoseconds a{-987654321987654321ll};
Nanoseconds b{a};
Seconds c{-543.21f};
Seconds d{c};
CORRADE_COMPARE(Long(b), -987654321987654321ll);
CORRADE_COMPARE(Float(d), -543.21f);
constexpr Nanoseconds ca{-987654321987654321ll};
constexpr Nanoseconds cb{ca};
constexpr Seconds cc{-543.21f};
constexpr Seconds cd{cc};
CORRADE_COMPARE(Long(cb), -987654321987654321ll);
CORRADE_COMPARE(Float(cd), -543.21f);
#ifndef CORRADE_NO_STD_IS_TRIVIALLY_TRAITS
CORRADE_VERIFY(std::is_trivially_copy_constructible<Nanoseconds>::value);
CORRADE_VERIFY(std::is_trivially_copy_constructible<Seconds>::value);
CORRADE_VERIFY(std::is_trivially_copy_assignable<Nanoseconds>::value);
CORRADE_VERIFY(std::is_trivially_copy_assignable<Seconds>::value);
#endif
CORRADE_VERIFY(std::is_nothrow_copy_constructible<Nanoseconds>::value);
CORRADE_VERIFY(std::is_nothrow_copy_constructible<Seconds>::value);
CORRADE_VERIFY(std::is_nothrow_copy_assignable<Nanoseconds>::value);
CORRADE_VERIFY(std::is_nothrow_copy_assignable<Seconds>::value);
}
void TimeTest::constructFromBase() {
/* The operation returns Unit instead of the leaf type, so this can work
only if the base class has a "copy constructor" from the base type */
Nanoseconds a = 15.0_usec + 3.5_msec;
Seconds b = Seconds{15.0_msec} + Seconds{3.5_sec};
CORRADE_COMPARE(a, 3.515_msec);
/* Comparing as seconds because precision loss involved */
CORRADE_COMPARE_AS(b, 3.515_sec, Seconds);
}
void TimeTest::convert() {
/* From external type */
Time a0{1707678819};
Keyframe b0{56.72f};
Nanoseconds a1{a0};
Seconds b1{b0};
CORRADE_COMPARE(a1, 1707678819.0_sec);
CORRADE_COMPARE_AS(b1, 56.72_sec, Seconds);
constexpr Time ca0{1707678819};
constexpr Keyframe cb0{56.72f};
constexpr Nanoseconds ca1{ca0};
constexpr Seconds cb1{cb0};
CORRADE_COMPARE(ca1, 1707678819.0_sec);
CORRADE_COMPARE_AS(cb1, 56.72_sec, Seconds);
/* To external type */
Nanoseconds c0 = 1707678819.0_sec;
Seconds d0 = 56.72_sec;
Time c1(c0);
Keyframe d1(d0);
CORRADE_COMPARE(c1.secondsSinceEpoch, 1707678819);
CORRADE_COMPARE(d1.duration, 56.72f);
constexpr Nanoseconds cc0 = 1707678819.0_sec;
constexpr Seconds cd0 = 56.72_sec;
constexpr Time cc1(cc0);
constexpr Keyframe cd1(cd0);
CORRADE_COMPARE(cc1.secondsSinceEpoch, 1707678819);
CORRADE_COMPARE(cd1.duration, 56.72f);
/* It should not be possible to convert in a direction that may result in a
precision loss, i.e. Seconds with a NanosecondsConverter */
CORRADE_VERIFY(std::is_constructible<Seconds, Keyframe>::value);
CORRADE_VERIFY(!std::is_constructible<Seconds, Time>::value);
CORRADE_VERIFY(std::is_constructible<Nanoseconds, Time>::value);
CORRADE_VERIFY(!std::is_constructible<Nanoseconds, Keyframe>::value);
CORRADE_VERIFY(std::is_constructible<Time, Nanoseconds>::value);
CORRADE_VERIFY(!std::is_constructible<Time, Seconds>::value);
CORRADE_VERIFY(std::is_constructible<Keyframe, Seconds>::value);
CORRADE_VERIFY(!std::is_constructible<Keyframe, Nanoseconds>::value);
/* Implicit conversion is not allowed */
CORRADE_VERIFY(!std::is_convertible<Keyframe, Seconds>::value);
CORRADE_VERIFY(!std::is_convertible<Time, Nanoseconds>::value);
CORRADE_VERIFY(!std::is_convertible<Nanoseconds, Time>::value);
CORRADE_VERIFY(!std::is_convertible<Seconds, Keyframe>::value);
}
void TimeTest::literals() {
/* Testing the full precision, 19 digits. Max representable 63-bit value is
9223372036854775807. */
auto a = 9087654321987654321_nsec;
auto b = 9087654321987654.321_usec;
auto c = 9087654321987.654321_msec;
auto d = 9087654321.987654321_sec;
CORRADE_VERIFY(std::is_same<decltype(a), Nanoseconds>::value);
CORRADE_VERIFY(std::is_same<decltype(b), Nanoseconds>::value);
CORRADE_VERIFY(std::is_same<decltype(c), Nanoseconds>::value);
CORRADE_VERIFY(std::is_same<decltype(d), Nanoseconds>::value);
CORRADE_COMPARE(Long(a), 9087654321987654321ll);
/* Not sure what Emscripten does here, but it behaves as if long double was
actually the full precision */
#if !defined(CORRADE_LONG_DOUBLE_SAME_AS_DOUBLE) || defined(CORRADE_TARGET_EMSCRIPTEN)
/* 80-bit long double has a 63-bit mantissa, which means this is converted
without any precision loss. Otherwise the precision is just 52 bits. */
CORRADE_COMPARE(Long(b), 9087654321987654321ll);
/* Well, almost. On x86 this conversion has a slight imprecision in the
lowest bit for the _ms variant, on ARM64 and Emscripten for the _s
variant. */
#if (defined(CORRADE_TARGET_ARM) && !defined(CORRADE_TARGET_32BIT)) || defined(CORRADE_TARGET_EMSCRIPTEN)
CORRADE_COMPARE(Long(c), 9087654321987654321ll);
CORRADE_COMPARE(Long(d), 9087654321987654320ll);
#else
CORRADE_COMPARE(Long(c), 9087654321987654320ll);
CORRADE_COMPARE(Long(d), 9087654321987654321ll);
#endif
#else
CORRADE_COMPARE(Long(b), 9087654321987653632ll);
CORRADE_COMPARE(Long(c), 9087654321987654656ll);
CORRADE_COMPARE(Long(d), 9087654321987653632ll);
#endif
constexpr auto ca = 9087654321987654321_nsec;
constexpr auto cb = 9087654321987654.321_usec;
constexpr auto cc = 9087654321987.654321_msec;
constexpr auto cd = 9087654321.987654321_sec;
CORRADE_VERIFY(std::is_same<decltype(ca), const Nanoseconds>::value);
CORRADE_VERIFY(std::is_same<decltype(cb), const Nanoseconds>::value);
CORRADE_VERIFY(std::is_same<decltype(cc), const Nanoseconds>::value);
CORRADE_VERIFY(std::is_same<decltype(cd), const Nanoseconds>::value);
CORRADE_COMPARE(Long(ca), 9087654321987654321ll);
#if !defined(CORRADE_LONG_DOUBLE_SAME_AS_DOUBLE) || defined(CORRADE_TARGET_EMSCRIPTEN)
CORRADE_COMPARE(Long(cb), 9087654321987654321ll);
#if (defined(CORRADE_TARGET_ARM) && !defined(CORRADE_TARGET_32BIT)) || defined(CORRADE_TARGET_EMSCRIPTEN)
CORRADE_COMPARE(Long(cc), 9087654321987654321ll);
CORRADE_COMPARE(Long(cd), 9087654321987654320ll);
#else
CORRADE_COMPARE(Long(cc), 9087654321987654320ll);
CORRADE_COMPARE(Long(cd), 9087654321987654321ll);
#endif
#else
CORRADE_COMPARE(Long(cb), 9087654321987653632ll);
CORRADE_COMPARE(Long(cc), 9087654321987654656ll);
CORRADE_COMPARE(Long(cd), 9087654321987653632ll);
#endif
}
void TimeTest::conversion() {
/* Implicit conversion should be allowed. Again testing (almost) the full
nanosecond precision, although not much of it is left when converting
to a 32-bit float. */
Nanoseconds a = Seconds{-987654321.987654321f};
Seconds b = 987654321987654321_nsec;
CORRADE_COMPARE(Long(a), -987654336000000000ll);
CORRADE_COMPARE(Float(b), 987654336.0f);
constexpr Nanoseconds ca = Seconds{987654321.987654321f};
constexpr Seconds cb = -987654321987654321_nsec;
CORRADE_COMPARE(Long(ca), 987654336000000000ll);
CORRADE_COMPARE(Float(cb), -987654336.0f);
CORRADE_VERIFY(std::is_nothrow_constructible<Nanoseconds, Seconds>::value);
CORRADE_VERIFY(std::is_nothrow_constructible<Seconds, Nanoseconds>::value);
}
void TimeTest::nanosecondFloatScaling() {
/* Nanoseconds is an integer type, but multiplying it with float should
give a reasonable output. The actual logic is in the Unit class, just
verify that it works from the high level perspective here. */
CORRADE_COMPARE(1000000000_nsec*1.25, 1250000000_nsec);
CORRADE_COMPARE(1000000000_nsec*1.25f, 1250000000_nsec);
CORRADE_COMPARE(1000000000_nsec/0.8, 1250000000_nsec);
CORRADE_COMPARE(1000000000_nsec/0.8f, 1250000000_nsec);
/* Compared to above this looks like it should "obviously work", although
internally both cases are the same, operating on Nanoseconds */
CORRADE_COMPARE(1.0_sec*1.25, 1.25_sec);
CORRADE_COMPARE(1.0_sec*1.25f, 1.25_sec);
CORRADE_COMPARE(1.0_sec/0.8, 1.25_sec);
CORRADE_COMPARE(1.0_sec/0.8f, 1.25_sec);
/* This would be nice if it worked, but so far it doesn't, as it's
calculated as an integer value */
CORRADE_COMPARE(1.0f/0.018f, 55.5556f);
{
CORRADE_EXPECT_FAIL("This doesn't work correctly.");
CORRADE_COMPARE(1.0_sec/18.0_msec, 55.5556f);
}
}
void TimeTest::debugNanoseconds() {
std::ostringstream out;
/* Also verify that the second expression compiles (it's the Unit type,
not Nanoseconds) */
Debug{&out} << 987654321987654321_nsec << 15.0_sec - 7.5_sec;
CORRADE_COMPARE(out.str(), "Nanoseconds(987654321987654321) Nanoseconds(7500000000)\n");
}
void TimeTest::debugNanosecondsPacked() {
std::ostringstream out;
/* Second is not packed, the first should not make any flags persistent */
Debug{&out} << Debug::packed << 15.0_sec << 45.0_sec;
CORRADE_COMPARE(out.str(), "15000000000 Nanoseconds(45000000000)\n");
}
void TimeTest::debugSeconds() {
std::ostringstream out;
/* Also verify that the second expression compiles (it's the Unit type,
not Nanoseconds) */
Debug{&out} << Seconds{123.45_sec} << Seconds{15.0_sec} - Seconds{7.5_sec};
CORRADE_COMPARE(out.str(), "Seconds(123.45) Seconds(7.5)\n");
}
void TimeTest::debugSecondsPacked() {
std::ostringstream out;
/* Second is not packed, the first should not make any flags persistent */
Debug{&out} << Debug::packed << Seconds{123.45_sec} << Seconds{45.0_sec};
CORRADE_COMPARE(out.str(), "123.45 Seconds(45)\n");
}
}}}}
CORRADE_TEST_MAIN(Magnum::Math::Test::TimeTest)