diff --git a/indra/alscript/masters/almasterwatch.cpp b/indra/alscript/masters/almasterwatch.cpp index 0f97654b79..7dd295ac76 100644 --- a/indra/alscript/masters/almasterwatch.cpp +++ b/indra/alscript/masters/almasterwatch.cpp @@ -26,6 +26,7 @@ #include "almasterwatch.h" +#include "almainloop.h" #include "alserialworker.h" #include "alwatchedfile.h" #include "llsingleton.h" @@ -74,7 +75,7 @@ namespace ALMasterWatch::look_t ALMasterWatch::offThread() { return [](std::vector paths, stamped_t found) { - const LL::WorkQueue::ptr_t main_loop = LL::WorkQueue::getInstance("mainloop"); + const LL::WorkQueue::ptr_t main_loop = ALMainLoop::queue(); if (!main_loop) { // No main loop to hand them back to -- a test -- so looked at diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 24ddb265ad..3584c24bbe 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -18,6 +18,7 @@ target_sources( llcommon PRIVATE alfilewrite.cpp + almainloop.cpp alserialworker.cpp alparamtype.cpp alrecoverystore.cpp @@ -122,6 +123,7 @@ target_sources( FILES alfilewrite.h allinebreaks.h + almainloop.h alserialworker.h alparamtype.h alrandmap.h diff --git a/indra/llcommon/almainloop.cpp b/indra/llcommon/almainloop.cpp new file mode 100644 index 0000000000..e0b6bef012 --- /dev/null +++ b/indra/llcommon/almainloop.cpp @@ -0,0 +1,57 @@ +/** + * @file almainloop.cpp + * @brief The queue the main loop services, and a test's say over it. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "almainloop.h" + +#include + +namespace +{ + // Read from the threads that ask whether to hand back, so a choice made + // on the main thread is seen whole. + std::atomic sChoice{ nullptr }; +} + +LL::WorkQueue::ptr_t ALMainLoop::queue() +{ + if (const Choice* choice = sChoice.load(std::memory_order_acquire)) + { + return choice->mChosen; + } + return LL::WorkQueue::getInstance("mainloop"); +} + +ALMainLoop::Choice::Choice(LL::WorkQueue* chosen) + : mChosen(chosen ? LL::WorkQueue::getInstance(chosen->getKey()) : nullptr) + , mOuter(sChoice.load(std::memory_order_relaxed)) +{ + sChoice.store(this, std::memory_order_release); +} + +ALMainLoop::Choice::~Choice() +{ + sChoice.store(mOuter, std::memory_order_release); +} diff --git a/indra/llcommon/almainloop.h b/indra/llcommon/almainloop.h new file mode 100644 index 0000000000..cdaa437f56 --- /dev/null +++ b/indra/llcommon/almainloop.h @@ -0,0 +1,58 @@ +/** + * @file almainloop.h + * @brief The queue the main loop services, and a test's say over it. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#pragma once + +#include "workqueue.h" + +// The queue the viewer's main loop services: what work done on another +// thread hands its result back through. Code that works off the main +// thread asks here rather than for the queue by its name, so that a test +// can say which way that code goes. +namespace ALMainLoop +{ + // The main loop's queue, or null where nothing services one -- a test + // that has not chosen one -- and the work is done in place instead. + LL_COMMON_API LL::WorkQueue::ptr_t queue(); + + // While one lives, queue() answers the queue it was given, or null for + // none, whatever queue of the main loop's name there is. Without it a + // queue one test made would choose the way for every test after it in + // the process. The newest living one wins. Main thread only. + class LL_COMMON_API Choice + { + public: + explicit Choice(LL::WorkQueue* chosen); + ~Choice(); + + Choice(const Choice&) = delete; + Choice& operator=(const Choice&) = delete; + + private: + friend LL::WorkQueue::ptr_t queue(); + + LL::WorkQueue::ptr_t mChosen; + const Choice* mOuter; + }; +} diff --git a/indra/llcommon/alwatchedfile.cpp b/indra/llcommon/alwatchedfile.cpp index 3ff4bd0bae..e5628d972e 100644 --- a/indra/llcommon/alwatchedfile.cpp +++ b/indra/llcommon/alwatchedfile.cpp @@ -26,6 +26,7 @@ #include "alwatchedfile.h" +#include "almainloop.h" #include "alserialworker.h" #include "fsyspath.h" #include "llerror.h" @@ -188,7 +189,7 @@ class ALWatchedFile::Watcher : public std::enable_shared_from_this { return; } - const LL::WorkQueue::ptr_t main = LL::WorkQueue::getInstance("mainloop"); + const LL::WorkQueue::ptr_t main = ALMainLoop::queue(); if (!mWorker) { mWorker = std::make_unique("WatchedFiles"); diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 009fcb1520..11b4268f0b 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -2217,8 +2217,7 @@ if(AL_BUILD_VIEWER) # The bundle's localised strings carry the version. file(READ "${CMAKE_CURRENT_SOURCE_DIR}/English.lproj/InfoPlist.strings" infoplist_strings) string( - REPLACE - "%%VERSION%%" + REPLACE "%%VERSION%%" "${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}" infoplist_strings "${infoplist_strings}" @@ -2450,47 +2449,7 @@ if(AL_BUILD_TESTS) lllogin ) set(test_project ${AL_VIEWER_BINARY_NAME}) - - # What Script Studio's tests have in common -- a tab, its save's flow, a - # notecard's items, the places and words, the account's recovery, and - # the units a unit under test is given -- - # built once for all of them, rather than once in each. Static: a test - # takes only the objects it uses, so one that fakes part of them keeps - # its fakes. - add_library( - al_studio_test_common - STATIC - alnotecardembedded.cpp - alrecovery.cpp - alscriptexternaleditor.cpp - alscriptlookup.cpp - alscriptnavigation.cpp - alscriptnoticebar.cpp - alscriptsaveflow.cpp - alscriptstudiodoc.cpp - alscriptstudiofileio.cpp - alscriptstudiofiles.cpp - alscriptstudioorphans.cpp - alscriptstudioplaces.cpp - alscriptstudiorecovery.cpp - alscriptstudioviewer.cpp - alscriptstudioweighing.cpp - alscriptstudiowords.cpp - alscripttypes.cpp - alscriptweightspane.cpp - tests/alscriptstudio_stubs.cpp - ) - get_property(studio_test_includes TARGET ${test_project} PROPERTY INTERFACE_INCLUDE_DIRECTORIES) - target_include_directories( - al_studio_test_common - PRIVATE - ${studio_test_includes} - ${INDRA_SOURCE_DIR}/test - ${INDRA_SOURCE_DIR}/llmath - ${INDRA_SOURCE_DIR}/llui - ) - target_link_libraries(al_studio_test_common PRIVATE al::flags PUBLIC llui alscript ${test_libs}) - set_target_properties(al_studio_test_common PROPERTIES FOLDER "Tests/${test_project}") + get_property(newview_includes TARGET ${test_project} PROPERTY INTERFACE_INCLUDE_DIRECTORIES) # What the tests take of the rest of the viewer, faked once. Objects, which # a test links whole, since what reaches for them is a library the linker @@ -2503,587 +2462,331 @@ if(AL_BUILD_TESTS) add_library(newview_test_settings OBJECT tests/newview_test_settings.cpp) target_link_libraries(newview_test_settings PRIVATE al::flags llxml) set_target_properties(newview_test_settings PROPERTIES FOLDER "Tests/${test_project}") - - # A unit test compiles one viewer source file on its own. Each line is one - # such file and its test under tests/. - foreach( - test - alagentlocationformat - alchattimestamp - alcolorwheelmodel - alcrashreporter - alcurvemodel - aldaycyclelandmarks - alfarplane - alfeedback - algradehistory - almotionpredictor - alsceneexplorerpredicate - alwhitebalancesolver - lldateutil - llviewerhelputil - llversioninfo - llvocache - lutcube - noise + # What Script Studio's units call of the rest of the viewer, which no test + # builds: one stand-in each. + add_library(newview_studio_test_stubs OBJECT tests/alscriptstudio_stubs.cpp) + target_include_directories( + newview_studio_test_stubs + PRIVATE ${newview_includes} ${INDRA_SOURCE_DIR}/test ${INDRA_SOURCE_DIR}/llmath + ) + target_link_libraries(newview_studio_test_stubs PRIVATE al::flags llui alscript ${test_libs}) + set_target_properties(newview_studio_test_stubs PROPERTIES FOLDER "Tests/${test_project}") + # The object cache as its test has it: with LL_TEST, which leaves out what + # reads the viewer's settings and the cull. On this object alone, since + # the viewer compiles the same file without it. + add_library(newview_test_llvocache OBJECT llvocache.cpp) + target_include_directories( + newview_test_llvocache + PRIVATE ${newview_includes} ${INDRA_SOURCE_DIR}/llmath + ) + target_compile_definitions(newview_test_llvocache PRIVATE "LL_TEST=llvocache" "LL_TEST_llvocache") + target_link_libraries(newview_test_llvocache PRIVATE al::flags llui ${test_libs}) + set_target_properties(newview_test_llvocache PROPERTIES FOLDER "Tests/${test_project}") + # What the viewer's tests share a process with, made before the first case + # and watched after each: the queue standing in for the main loop's, the + # recovery writer's thread, the settings groups, Script Studio's recovery, + # history and viewer, and llcommon's checks but for its event pumps made + # afresh for each case, which the viewer's own event APIs listen on from + # before main. llui's and llrender's checks make the UI and watch it. + add_library(newview_test_checks OBJECT tests/newview_test_checks.cpp) + target_include_directories( + newview_test_checks + PRIVATE ${newview_includes} ${INDRA_SOURCE_DIR}/llmath ) - al_add_test(${test} UNIT PROJECT ${test_project} LIBRARIES ${test_libs}) - endforeach() - # A parcel's information handed to its observers and to the URL entries - # that name it: llui's own, rather than stubs of them. - al_add_test( - llremoteparcelrequest - UNIT - PROJECT ${test_project} - LIBRARIES newview_test_fakes llui ${test_libs} + target_link_libraries( + newview_test_checks + PRIVATE al::flags al_test_support llui alscript ${test_libs} ) - # The agent's maturity, read and written through a settings group of each - # test's own. - al_add_test(llagentaccess UNIT PROJECT ${test_project} LIBRARIES llxml ${test_libs}) - # The vectorscope bins on the colour wheels' own chroma plane, so the scope - # data test links the model that defines it rather than keeping a second copy - # of the basis that could drift from it. - al_add_test( - alscopedata - UNIT + set_target_properties(newview_test_checks PROPERTIES FOLDER "Tests/${test_project}") + # A viewer texture's boost level, which the world map's tests reach. + add_library(newview_test_viewertexture OBJECT tests/llviewertexture_stub.cpp) + target_include_directories( + newview_test_viewertexture + PRIVATE ${newview_includes} ${INDRA_SOURCE_DIR}/llmath + ) + target_link_libraries(newview_test_viewertexture PRIVATE al::flags llui ${test_libs}) + set_target_properties(newview_test_viewertexture PROPERTIES FOLDER "Tests/${test_project}") + + # The viewer's own code, in one binary, each test file run in a process of + # its own. There is no library of the viewer to link, so each file of it a + # test needs is compiled here, once. The UI is up before the first case, + # as llui's tests have it, from the source tree's skins and fonts. + al_add_test_binary( + newview_tests PROJECT ${test_project} - LIBRARIES ${test_libs} - SOURCES alcolorwheelmodel.cpp - ) - # The selection outline's id and edge passes, the viewer's own shader files - # read from the source tree, on LLWindowManager's hidden window. - al_add_test( - alselectionoutline_gl - GL + SOURCES + tests/alagentlocationformat_test.cpp + tests/alchattimestamp_test.cpp + tests/alcolorwheelmodel_test.cpp + tests/alcrashreporter_test.cpp + tests/alcurvemodel_test.cpp + tests/aldaycyclelandmarks_test.cpp + tests/alfarplane_test.cpp + tests/alfeedback_test.cpp + tests/alfeedbackoutbox_test.cpp + tests/algradehistory_test.cpp + tests/almotionpredictor_test.cpp + tests/alnotecardembedded_test.cpp + tests/alrecoverykeeper_test.cpp + tests/alsceneexplorerpredicate_test.cpp + tests/alscopedata_test.cpp + tests/alscriptanalysislane_test.cpp + tests/alscriptanalyzers_test.cpp + tests/alscriptcontentsindex_test.cpp + tests/alscriptcrumbsbar_test.cpp + tests/alscriptexplorermodel_test.cpp + tests/alscriptexplorertree_test.cpp + tests/alscriptexternaleditor_test.cpp + tests/alscriptinspectorpane_test.cpp + tests/alscriptitemdrop_test.cpp + tests/alscriptjobqueue_test.cpp + tests/alscriptkeymap_test.cpp + tests/alscriptlookup_test.cpp + tests/alscriptnavigation_test.cpp + tests/alscriptobjectcheck_test.cpp + tests/alscriptoutlinepane_test.cpp + tests/alscriptoutputpane_test.cpp + tests/alscriptproblemspane_test.cpp + tests/alscriptrecompile_test.cpp + tests/alscriptreferencespane_test.cpp + tests/alscriptregionusage_test.cpp + tests/alscriptsaveflow_test.cpp + tests/alscriptsearchpane_test.cpp + tests/alscriptsnippets_test.cpp + tests/alscriptstudioaccount_test.cpp + tests/alscriptstudiocaret_test.cpp + tests/alscriptstudiochecking_test.cpp + tests/alscriptstudiocommands_test.cpp + tests/alscriptstudiocomparepairs_test.cpp + tests/alscriptstudiocomparewith_test.cpp + tests/alscriptstudiodoc_test.cpp + tests/alscriptstudioexpandedcompare_test.cpp + tests/alscriptstudiofiles_test.cpp + tests/alscriptstudiohistory_test.cpp + tests/alscriptstudiomasters_test.cpp + tests/alscriptstudiomerging_test.cpp + tests/alscriptstudioorphans_test.cpp + tests/alscriptstudiorecovery_test.cpp + tests/alscriptstudiosaving_test.cpp + tests/alscriptstudioselections_test.cpp + tests/alscriptstudiovim_test.cpp + tests/alscriptstudioweighing_test.cpp + tests/alscriptstudiowords_test.cpp + tests/alscripttheme_test.cpp + tests/alscripttypes_test.cpp + tests/alscriptweightspane_test.cpp + tests/alterrainsurfacemaps_test.cpp + tests/altexturetable_test.cpp + tests/alwhitebalancesolver_test.cpp + tests/cppfeatures_test.cpp + tests/llagentaccess_test.cpp + tests/llhttpretrypolicy_test.cpp + tests/llversioninfo_test.cpp + tests/llviewerassetstats_test.cpp + tests/llviewercontrollistener_test.cpp + tests/llvocache_test.cpp + tests/lutcube_test.cpp + tests/noise_test.cpp + UNIT + alagentlocationformat.cpp + alchattimestamp.cpp + alcolorwheelmodel.cpp + alcrashreporter.cpp + alcurvemodel.cpp + aldaycyclelandmarks.cpp + alfarplane.cpp + alfeedback.cpp + alfeedbackoutbox.cpp + algradehistory.cpp + almotionpredictor.cpp + alnotecardembedded.cpp + alrecovery.cpp + alrecoverykeeper.cpp + alsceneexplorerpredicate.cpp + alscopedata.cpp + alscriptanalysislane.cpp + alscriptanalyzers.cpp + alscriptcontentsindex.cpp + alscriptcrumbsbar.cpp + alscriptexplorermodel.cpp + alscriptexplorertree.cpp + alscriptexternaleditor.cpp + alscriptinspectorpane.cpp + alscriptitemdrop.cpp + alscriptkeymap.cpp + alscriptkeypresets.cpp + alscriptlookup.cpp + alscriptnavigation.cpp + alscriptnoticebar.cpp + alscriptobjectcheck.cpp + alscriptoutlinepane.cpp + alscriptoutputpane.cpp + alscriptproblemspane.cpp + alscriptrecompile.cpp + alscriptreferencespane.cpp + alscriptregionusage.cpp + alscriptsaveflow.cpp + alscriptsearch.cpp + alscriptsearchpane.cpp + alscriptsnippets.cpp + alscriptstudioaccount.cpp + alscriptstudiocaret.cpp + alscriptstudiochecking.cpp + alscriptstudiocommands.cpp + alscriptstudiocomparepairs.cpp + alscriptstudiocomparewith.cpp + alscriptstudiodoc.cpp + alscriptstudioexpandedcompare.cpp + alscriptstudiofileio.cpp + alscriptstudiofiles.cpp + alscriptstudiohistory.cpp + alscriptstudiomasters.cpp + alscriptstudiomerging.cpp + alscriptstudioorphans.cpp + alscriptstudioplaces.cpp + alscriptstudiorecovery.cpp + alscriptstudiosaving.cpp + alscriptstudioselections.cpp + alscriptstudioviewer.cpp + alscriptstudiovim.cpp + alscriptstudioweighing.cpp + alscriptstudiowords.cpp + alscripttheme.cpp + alscripttypes.cpp + alscriptweightspane.cpp + alwhitebalancesolver.cpp + llagentaccess.cpp + llhttpretrypolicy.cpp + llversioninfo.cpp + llviewerassetstats.cpp + llviewercontrollistener.cpp + lutcube.cpp + noise.cpp + LIBRARIES llui alscript ll::slua llxml ${test_libs} + SUPPORT + newview_test_fakes + newview_test_settings + newview_studio_test_stubs + newview_test_llvocache + newview_test_checks + llui_test_checks + llrender_test_checks + DEFINES + "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" + "AL_LSL_DEFINITIONS_DIR=\"${LSL_DEFINITIONS_DIR}\"" + ) + + # The selection outline's id and edge passes, and the window's depth under + # the HUD, the viewer's own shader files read from the source tree, on + # llrender's shared hidden window. + al_add_test_binary( + newview_gl_tests PROJECT ${test_project} + SOURCES tests/alhuddepth_gl_test.cpp tests/alselectionoutline_gl_test.cpp LIBRARIES llrender + SUPPORT llrender_test_checks llcommon_test_checks INCLUDES ${INDRA_SOURCE_DIR}/llprimitive DEFINES "AL_TEST_SHADER_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/app_settings/shaders\"" - ) - # The window's depth under the HUD: cleared in the HUD's rects, then the - # scene's written back with the viewer's own copy shader. - al_add_test( - alhuddepth_gl GL - PROJECT ${test_project} - LIBRARIES llrender - DEFINES "AL_TEST_SHADER_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/app_settings/shaders\"" - ) - # The outbox keeps what the pure feedback code makes. - al_add_test( - alfeedbackoutbox - UNIT - PROJECT ${test_project} - LIBRARIES ${test_libs} - SOURCES alfeedback.cpp - ) - al_add_test( - lllogininstance - UNIT - PROJECT ${test_project} - LIBRARIES ${test_libs} - SOURCES llversioninfo.cpp ) - al_add_test( - llworldmap - UNIT + + # Linden's tests that stand in for parts of the viewer, llui and llxml by + # hand, each in its own way, so that no two could share a binary: a + # binary each, until one set of fakes replaces their stubs. Most take + # llui's headers, which the viewer's precompiled header names, without its + # library, whose code they stub. + set(llui_headers $) + al_add_test_binary( + lldateutil_test PROJECT ${test_project} + SOURCES tests/lldateutil_test.cpp + UNIT lldateutil.cpp LIBRARIES ${test_libs} - SOURCES tests/llviewertexture_stub.cpp + INCLUDES ${llui_headers} ) - al_add_test( - llworldmipmap - UNIT + al_add_test_binary( + lllogininstance_test PROJECT ${test_project} + SOURCES tests/lllogininstance_test.cpp + UNIT lllogininstance.cpp llversioninfo.cpp LIBRARIES ${test_libs} - SOURCES tests/llviewertexture_stub.cpp - ) - - # An inventory item dropped on a script: its name or its key, over the - # studio's own window. - al_add_test( - alscriptitemdrop - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # A notecard's items in its text, over a fake of the viewer's side. - al_add_test( - alnotecardembedded - UNIT - PROJECT ${test_project} - LIBRARIES newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # What each prim holds, as one index for every studio window, over a - # fake of the world. - al_add_test( - alscriptcontentsindex - UNIT - PROJECT ${test_project} - LIBRARIES llui alscript ${test_libs} - ) - - # What the region reserves for the objects in hand, the world faked: the - # answers read, each object asked once a minute, the attachments at once. - al_add_test(alscriptregionusage UNIT PROJECT ${test_project} LIBRARIES llui alscript ${test_libs}) - - # What the Script Studio's explorer lists, and the rows it makes of them, - # with nothing of the world. - al_add_test( - alscriptexplorermodel - UNIT - PROJECT ${test_project} - LIBRARIES newview_test_fakes llui alscript ${test_libs} - SOURCES alscriptcontentsindex.cpp - ) - - # The explorer's rows shown as the inventory's folder view, from the source - # tree's skins and fonts. - al_add_test( - alscriptexplorertree - UNIT - PROJECT ${test_project} - LIBRARIES newview_test_fakes llui alscript ${test_libs} - SOURCES alscriptexplorermodel.cpp alscriptcontentsindex.cpp - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # The Script Studio's Output tab over the studio's own window, the - # window's side faked. - al_add_test( - alscriptoutputpane - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # The Script Studio's Problems tab over the studio's own window, the - # window's side faked, and a tab's problems gathered for it. - al_add_test( - alscriptproblemspane - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # The Script Studio's Search tab over the studio's own window, the - # window's side faked; and what Replace All does with a script found. - al_add_test( - alscriptsearchpane - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - SOURCES alscriptsearch.cpp - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # A window's unsaved text kept in a recovery store of the test's. - al_add_test( - alrecoverykeeper - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - ) - - # A Script Studio window's saves kept in a history of the test's, listed, - # compared and put back over tabs of their own editors. - al_add_test( - alscriptstudiohistory - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # A Script Studio window's Compare With over tabs of their own editors: - # what a tab is offered, and each kind of thing chosen set beside it. - al_add_test( - alscriptstudiocomparewith - UNIT - PROJECT ${test_project} - SOURCES alscriptstudiohistory.cpp - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # A Script Studio window's comparison lined up by its texts' functions, - # over tabs of their own editors: each text's outline asked for as it is - # another, and the two paired. - al_add_test( - alscriptstudiocomparepairs - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # A Script Studio window's source beside what a save sends of it, lined - # up by the preprocessor's map, over tabs of their own editors. - al_add_test( - alscriptstudioexpandedcompare - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # A Script Studio window's merges over tabs of their own editors and - # comparisons: begun, settled, and compared where they cannot be. - al_add_test( - alscriptstudiomerging - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # A Script Studio window's selections held and compared, over tabs of - # their own editors and comparisons. - al_add_test( - alscriptstudioselections - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # A Script Studio window's unsaved texts kept in a recovery store of the - # test's, and taken up again over tabs of their own editors. - al_add_test( - alscriptstudiorecovery - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" + INCLUDES ${llui_headers} ) - - # The scripter's snippets: the shipped and their own, read, written and - # kept whole on disk. - al_add_test( - alscriptsnippets - UNIT + # A parcel's information handed to its observers and to the URL entries + # that name it: llui's own, rather than stubs of them. + al_add_test_binary( + llremoteparcelrequest_test PROJECT ${test_project} + SOURCES tests/llremoteparcelrequest_test.cpp + UNIT llremoteparcelrequest.cpp LIBRARIES llui ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" + SUPPORT newview_test_fakes ) - - # Script Studio's themes: their colours, the shipped ones legible, and - # every skin's script colours read against what they are drawn on. - al_add_test( - alscripttheme - UNIT + al_add_test_binary( + llsecapi_test PROJECT ${test_project} - LIBRARIES newview_test_fakes llui ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # The Script Studio's commands by name, with nothing of the window. - al_add_test(alscriptstudiocommands UNIT PROJECT ${test_project} LIBRARIES ${test_libs}) - - # Script Studio's keys: the editors' keymap and the menus' keys, with a - # person's changes over them in a setting of the test's. - al_add_test( - alscriptkeymap - UNIT - PROJECT ${test_project} - SOURCES alscriptkeypresets.cpp - LIBRARIES newview_test_fakes newview_test_settings llui ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # Whole saves of a Script Studio tab through where they stand and where - # they go, with nothing of the viewer. - al_add_test(alscriptsaveflow UNIT PROJECT ${test_project} LIBRARIES llui alscript ${test_libs}) - - # Whole saves of Script Studio tabs done -- tidied, checked, preprocessed, - # sent, answered -- over the window's side faked. - al_add_test( - alscriptstudiosaving - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # Script Studio's files on disk, over the window's side faked: languages, - # the recent lists, writing, changes outside, loading, Save As. - al_add_test( - alscriptstudiofiles - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # A Script Studio window's side of scripts mastered by files on disk, over - # the window's side faked and the account's links held in memory: what may - # be linked, a tab linked and what was typed in it, and a tab giving way - # to its file. - al_add_test( - alscriptstudiomasters - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # What of Script Studio's state is an account's own, kept apart from the - # shared and put back over it. - al_add_test(alscriptstudioaccount UNIT PROJECT ${test_project} LIBRARIES ${test_libs}) - - # Script Studio's navigation: the history of places, back and forward, - # and previews opened as a list is walked. - al_add_test( - alscriptnavigation - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # Script Studio's lookups across scripts, the window's side and the - # preprocessor's names faked: the fan-out, answers dropped, renames. - al_add_test( - alscriptlookup - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # The Script Studio's References tab over the studio's own window, the - # window's side faked: listed, slid with edits, chosen, sorted. - al_add_test( - alscriptreferencespane - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # Script Studio's outline over the studio's own window, the window's - # side faked: the tree, folds, the filter, sorting, the caret followed. - al_add_test( - alscriptoutlinepane - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # Script Studio's bar under the editor over the studio's own window, the - # window's side faked: the path of symbols, the trailer's parts, a step. - al_add_test( - alscriptcrumbsbar - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # Script Studio's inspector over the studio's own window, the window's - # side faked: a name's declaration, words and page, and the problems. - al_add_test( - alscriptinspectorpane - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # Script Studio's name at the caret, the window's side faked: definitions - # gone to, references read back and asked for, the caret watched. - al_add_test( - alscriptstudiocaret - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # What waits for the script analysis thread: the latest of each kind - # about each script, by rank, a stale text passed over. - al_add_test(alscriptjobqueue PROJECT ${test_project} LIBRARIES llcommon) - - # One engine's analysis thread, over analyzers made to wait: a long - # check on one lane holding nothing of the other's, and another tab's - # check giving way to a question of the tab in front. - al_add_test( - alscriptanalysislane - UNIT - PROJECT ${test_project} - LIBRARIES alscript llui ll::slua ${test_libs} - ) - - # Each language's analyzer, asked as the analysis thread asks it, over - # the grid's own definitions. - al_add_test( - alscriptanalyzers - UNIT - PROJECT ${test_project} - LIBRARIES newview_test_fakes alscript llui ${test_libs} - DEFINES "AL_LSL_DEFINITIONS_DIR=\"${LSL_DEFINITIONS_DIR}\"" - ) - - # Script Studio's checking, the window's side and the viewer's sources - # faked: checks due and answered, expansions, imports, lints, fixes. - al_add_test( - alscriptstudiochecking - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # A check of every script of an object, the window and the world faked: - # the open ones left to their tabs, the rest read, expanded and asked. - al_add_test( - alscriptobjectcheck - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - SOURCES alscriptstudiochecking.cpp - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # A compile's result read back to the source it was of: under the - # envelope's lines, through the expansion's map. - al_add_test(alscripttypes UNIT PROJECT ${test_project} LIBRARIES alscript ${test_libs}) - - # A recompile from the Explorer, the window and the world faked: scripts - # and prims walked a few at a time, each said, a closed one's problems - # read back through its expansion. - al_add_test( - alscriptrecompile - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # Script Studio's orphans, the world's reach faked: what a tab has become, - # the check, reattaching, and the notice over the editor with its bar. - al_add_test( - alscriptstudioorphans - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # Script Studio's words: the vocabulary from definitions a test gives, - # editors taught it, hovers, completions, the Insert menu, the reference. - al_add_test( - alscriptstudiowords - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # Script Studio's weighing, over the window's side and the analyzers - # faked: targets, answers, what a save sends, notes and heat, fixes. - al_add_test( - alscriptstudioweighing - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # Script Studio's tabs held open in an editor outside, over the window's - # side faked: the copy, its saves taken or held, the log, letting go. - al_add_test( - alscriptexternaleditor - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # Script Studio's side of vim over the window's commands and its side - # faked: the : commands, :set, :history, q:, = and the banner. - al_add_test( - alscriptstudiovim - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - SOURCES alscriptstudiocommands.cpp - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # A Script Studio tab's own logic, and the fake of the window's services - # the units split out of the window are tested with. - al_add_test( - alscriptstudiodoc - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # The Script Studio's Weights tab over the studio's own window, from the - # source tree's skins and fonts as llui's widget tests have them. - al_add_test( - alscriptweightspane - UNIT - PROJECT ${test_project} - LIBRARIES al_studio_test_common newview_test_fakes llui alscript ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" + SOURCES tests/llsecapi_test.cpp + UNIT llsecapi.cpp + LIBRARIES ${test_libs} + INCLUDES ${llui_headers} ) - - al_add_test(cppfeatures PROJECT ${test_project} LIBRARIES ${test_libs}) - al_add_test(altexturetable PROJECT ${test_project} LIBRARIES ${test_libs}) - # The map layout and the neighbour walk are inline in the header; the GL - # and surface side stays out of the test. - al_add_test(alterrainsurfacemaps PROJECT ${test_project} LIBRARIES ${test_libs}) - al_add_test( - llsechandler_basic + al_add_test_binary( + llsechandler_basic_test PROJECT ${test_project} + SOURCES tests/llsechandler_basic_test.cpp + UNIT llsechandler_basic.cpp LIBRARIES ${test_libs} - SOURCES llsechandler_basic.cpp + INCLUDES ${llui_headers} ) - al_add_test(llsecapi PROJECT ${test_project} LIBRARIES ${test_libs} SOURCES llsecapi.cpp) - al_add_test( - llslurl + al_add_test_binary( + llslurl_test PROJECT ${test_project} + SOURCES tests/llslurl_test.cpp + UNIT llslurl.cpp llviewernetwork.cpp LIBRARIES ${test_libs} - SOURCES llslurl.cpp llviewernetwork.cpp + INCLUDES ${llui_headers} ) - al_add_test( - llviewercontrollistener + al_add_test_binary( + lltextureinfo_test PROJECT ${test_project} + SOURCES tests/lltextureinfo_test.cpp + UNIT lltextureinfo.cpp LIBRARIES llxml ${test_libs} - SOURCES llviewercontrollistener.cpp + SUPPORT newview_test_settings + INCLUDES ${llui_headers} ) - al_add_test( - llviewernetwork + al_add_test_binary( + llviewerhelputil_test PROJECT ${test_project} + SOURCES tests/llviewerhelputil_test.cpp + UNIT llviewerhelputil.cpp LIBRARIES ${test_libs} - SOURCES llviewernetwork.cpp + INCLUDES ${llui_headers} ) - al_add_test( - llviewerassetstats + al_add_test_binary( + llviewernetwork_test PROJECT ${test_project} + SOURCES tests/llviewernetwork_test.cpp + UNIT llviewernetwork.cpp LIBRARIES ${test_libs} - SOURCES llviewerassetstats.cpp + INCLUDES ${llui_headers} ) - al_add_test( - llhttpretrypolicy + al_add_test_binary( + llworldmap_test PROJECT ${test_project} + SOURCES tests/llworldmap_test.cpp + UNIT llworldmap.cpp LIBRARIES ${test_libs} - SOURCES llhttpretrypolicy.cpp + SUPPORT newview_test_viewertexture + INCLUDES ${llui_headers} ) - al_add_test( - lltextureinfo + al_add_test_binary( + llworldmipmap_test PROJECT ${test_project} - LIBRARIES newview_test_settings llxml ${test_libs} - SOURCES lltextureinfo.cpp + SOURCES tests/llworldmipmap_test.cpp + UNIT llworldmipmap.cpp + LIBRARIES ${test_libs} + SUPPORT newview_test_viewertexture + INCLUDES ${llui_headers} ) endif() diff --git a/indra/newview/alscriptlinkbadges.cpp b/indra/newview/alscriptlinkbadges.cpp index c25473a18a..141d2902a1 100644 --- a/indra/newview/alscriptlinkbadges.cpp +++ b/indra/newview/alscriptlinkbadges.cpp @@ -26,6 +26,7 @@ #include "alscriptlinkbadges.h" +#include "almainloop.h" #include "alscriptdiskmasters.h" #include "alserialworker.h" #include "workqueue.h" @@ -173,7 +174,7 @@ void ALScriptLinkBadges::look() } // Where there is no main loop to hand what was found back to -- a test // -- looked at here. - const LL::WorkQueue::ptr_t main_loop = LL::WorkQueue::getInstance("mainloop"); + const LL::WorkQueue::ptr_t main_loop = ALMainLoop::queue(); if (!main_loop) { Stamps stamps; diff --git a/indra/newview/alscriptlinkscripts.cpp b/indra/newview/alscriptlinkscripts.cpp index e5f6c88faa..094404f91d 100644 --- a/indra/newview/alscriptlinkscripts.cpp +++ b/indra/newview/alscriptlinkscripts.cpp @@ -26,6 +26,7 @@ #include "alscriptlinkscripts.h" +#include "almainloop.h" #include "almastermatch.h" #include "alscriptcontentsindex.h" #include "alscriptdiskmasters.h" @@ -394,7 +395,7 @@ void ALScriptLinkScripts::findFiles() // a drive that may be slow. Where there is no main loop to hand what it // found back to -- a test -- looked at here. const U32 generation = mGeneration; - const LL::WorkQueue::ptr_t main_loop = LL::WorkQueue::getInstance("mainloop"); + const LL::WorkQueue::ptr_t main_loop = ALMainLoop::queue(); if (!main_loop) { found(generation, match(asks, lsl, lua, {})); diff --git a/indra/newview/alscriptlookup.cpp b/indra/newview/alscriptlookup.cpp index b687d3a3ca..e5c78527b5 100644 --- a/indra/newview/alscriptlookup.cpp +++ b/indra/newview/alscriptlookup.cpp @@ -28,6 +28,7 @@ #include "aldiskincludes.h" #include "alincludeidentity.h" +#include "almainloop.h" #include "alscriptlexicon.h" #include "alscriptnavigation.h" #include "alscriptstudioanalysis.h" @@ -352,7 +353,7 @@ void ALScriptLookup::walkDisk(const std::string& id, U32 generation, std::vector // Off the main thread: a walk may look at thousands of entries and // read hundreds of files, on a drive that may be slow. Where there is // no main loop to hand what it read back to -- a test -- walked here. - const LL::WorkQueue::ptr_t main_loop = LL::WorkQueue::getInstance("mainloop"); + const LL::WorkQueue::ptr_t main_loop = ALMainLoop::queue(); if (!main_loop) { walked(id, generation, readDisk(folders, lua, name, listed, {}), nullptr); diff --git a/indra/newview/alscriptmasterupload.cpp b/indra/newview/alscriptmasterupload.cpp index b28f1393a8..a79039cac6 100644 --- a/indra/newview/alscriptmasterupload.cpp +++ b/indra/newview/alscriptmasterupload.cpp @@ -27,6 +27,7 @@ #include "alscriptmasterupload.h" #include "alfilewrite.h" +#include "almainloop.h" #include "alrecovery.h" #include "alrecoverystore.h" #include "alsavehistory.h" @@ -169,7 +170,7 @@ void ALScriptMasterUpload::read() // to this send on the main thread, which alone goes on with it. With no // main loop to hand it back to -- a test -- or the thread closed as the // viewer goes, read here. - if (const LL::WorkQueue::ptr_t main_loop = LL::WorkQueue::getInstance("mainloop")) + if (const LL::WorkQueue::ptr_t main_loop = ALMainLoop::queue()) { std::shared_ptr self = shared_from_this(); const std::string master = mLink.master; @@ -408,7 +409,7 @@ void ALScriptMasterUpload::askHistory() // given to keep before this; what it found handed back to the main // thread. With no main loop to hand it back to -- a test -- read here; // with the thread stopped, as the viewer quits, not read at all. - const LL::WorkQueue::ptr_t main_loop = LL::WorkQueue::getInstance("mainloop"); + const LL::WorkQueue::ptr_t main_loop = ALMainLoop::queue(); if (!main_loop) { historyHas(history->holds(key, asset, *text, HISTORY_NEWEST)); diff --git a/indra/newview/alscriptstudiomasters.cpp b/indra/newview/alscriptstudiomasters.cpp index 7fef5bb221..c7816fa74e 100644 --- a/indra/newview/alscriptstudiomasters.cpp +++ b/indra/newview/alscriptstudiomasters.cpp @@ -28,6 +28,7 @@ #include "alcodeeditor.h" #include "alfilewrite.h" +#include "almainloop.h" #include "almastermatch.h" #include "alnotecardembedded.h" #include "alscriptenvelope.h" @@ -382,7 +383,7 @@ void ALScriptStudioMasters::loaded(Doc& doc) std::vector> aliases = mDiskMasters.aliasesFor(std::string(), lua); const U32 look = ++doc.master->hintLook; const std::string id = doc.id; - const LL::WorkQueue::ptr_t main_loop = LL::WorkQueue::getInstance("mainloop"); + const LL::WorkQueue::ptr_t main_loop = ALMainLoop::queue(); if (!main_loop) { // No main loop to hand it back to -- a test -- so looked for here. @@ -621,7 +622,7 @@ void ALScriptStudioMasters::lookAgain() { return; } - const LL::WorkQueue::ptr_t main_loop = LL::WorkQueue::getInstance("mainloop"); + const LL::WorkQueue::ptr_t main_loop = ALMainLoop::queue(); if (!main_loop) { return; diff --git a/indra/newview/tests/alagentlocationformat_test.cpp b/indra/newview/tests/alagentlocationformat_test.cpp index e932ac6bda..3f35a050b8 100644 --- a/indra/newview/tests/alagentlocationformat_test.cpp +++ b/indra/newview/tests/alagentlocationformat_test.cpp @@ -26,9 +26,9 @@ #include "../alagentlocationformat.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { struct alagentlocationformat_data { @@ -52,135 +52,110 @@ namespace tut return out; } }; - typedef test_group factory; - typedef factory::object object; } namespace -{ - tut::factory tf("ALAgentLocationFormat"); -} - -namespace tut { // A parcel with a name is what most of these show. - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(alagentlocationformat_data, "ALAgentLocationFormat: what each format shows of a named parcel", "[ALAgentLocationFormat]") { - ensure_equals("NORMAL is the parcel and the region", - named(LLAgentUI::LOCATION_FORMAT_NORMAL), + UNSCOPED_INFO("NORMAL is the parcel and the region"); + CHECK(named(LLAgentUI::LOCATION_FORMAT_NORMAL) == std::string("Ye Olde Parcel, Hippotropolis")); - ensure_equals("NORMAL_COORDS is the parcel and the position, no region", - named(LLAgentUI::LOCATION_FORMAT_NORMAL_COORDS), + UNSCOPED_INFO("NORMAL_COORDS is the parcel and the position, no region"); + CHECK(named(LLAgentUI::LOCATION_FORMAT_NORMAL_COORDS) == std::string("Ye Olde Parcel (128, 64, 22)")); - ensure_equals("LANDMARK is the parcel alone", - named(LLAgentUI::LOCATION_FORMAT_LANDMARK), + UNSCOPED_INFO("LANDMARK is the parcel alone"); + CHECK(named(LLAgentUI::LOCATION_FORMAT_LANDMARK) == std::string("Ye Olde Parcel")); - ensure_equals("NO_MATURITY is everything but the rating", - named(LLAgentUI::LOCATION_FORMAT_NO_MATURITY), + UNSCOPED_INFO("NO_MATURITY is everything but the rating"); + CHECK(named(LLAgentUI::LOCATION_FORMAT_NO_MATURITY) == std::string("Ye Olde Parcel, Hippotropolis (128, 64, 22)")); - ensure_equals("NO_COORDS is everything but the position", - named(LLAgentUI::LOCATION_FORMAT_NO_COORDS), + UNSCOPED_INFO("NO_COORDS is everything but the position"); + CHECK(named(LLAgentUI::LOCATION_FORMAT_NO_COORDS) == std::string("Ye Olde Parcel, Hippotropolis - Adult")); - ensure_equals("FULL is everything", - named(LLAgentUI::LOCATION_FORMAT_FULL), + UNSCOPED_INFO("FULL is everything"); + CHECK(named(LLAgentUI::LOCATION_FORMAT_FULL) == std::string("Ye Olde Parcel, Hippotropolis (128, 64, 22) - Adult")); } // An unnamed parcel drops out entirely rather than leaving its comma // behind, and the region takes its place. - template<> template<> - void object::test<2>() + TEST_CASE_METHOD(alagentlocationformat_data, "ALAgentLocationFormat: an unnamed parcel drops out and the region takes its place", "[ALAgentLocationFormat]") { - ensure_equals("NORMAL falls back to the region", - unnamed(LLAgentUI::LOCATION_FORMAT_NORMAL), + CHECK(unnamed(LLAgentUI::LOCATION_FORMAT_NORMAL) == std::string("Hippotropolis")); - ensure_equals("NORMAL_COORDS falls back to the region", - unnamed(LLAgentUI::LOCATION_FORMAT_NORMAL_COORDS), + CHECK(unnamed(LLAgentUI::LOCATION_FORMAT_NORMAL_COORDS) == std::string("Hippotropolis (128, 64, 22)")); - ensure_equals("LANDMARK falls back to the region", - unnamed(LLAgentUI::LOCATION_FORMAT_LANDMARK), + CHECK(unnamed(LLAgentUI::LOCATION_FORMAT_LANDMARK) == std::string("Hippotropolis")); // The one place the two branches agree on the answer but reach it // differently: with no parcel to name, NO_MATURITY has nothing left to // distinguish it from NORMAL_COORDS. - ensure_equals("NO_MATURITY falls back to the region", - unnamed(LLAgentUI::LOCATION_FORMAT_NO_MATURITY), + CHECK(unnamed(LLAgentUI::LOCATION_FORMAT_NO_MATURITY) == std::string("Hippotropolis (128, 64, 22)")); - ensure_equals("NO_COORDS falls back to the region", - unnamed(LLAgentUI::LOCATION_FORMAT_NO_COORDS), + CHECK(unnamed(LLAgentUI::LOCATION_FORMAT_NO_COORDS) == std::string("Hippotropolis - Adult")); - ensure_equals("FULL falls back to the region", - unnamed(LLAgentUI::LOCATION_FORMAT_FULL), + CHECK(unnamed(LLAgentUI::LOCATION_FORMAT_FULL) == std::string("Hippotropolis (128, 64, 22) - Adult")); } // No rating means no separator either, in both branches. A trailing " - " // is what forgetting this looks like. - template<> template<> - void object::test<3>() + TEST_CASE_METHOD(alagentlocationformat_data, "ALAgentLocationFormat: no rating leaves no separator for named or unnamed parcels", "[ALAgentLocationFormat]") { - ensure_equals("a named parcel with no rating has no separator", - named(LLAgentUI::LOCATION_FORMAT_NO_COORDS, ""), + CHECK(named(LLAgentUI::LOCATION_FORMAT_NO_COORDS, "") == std::string("Ye Olde Parcel, Hippotropolis")); - ensure_equals("nor does FULL", - named(LLAgentUI::LOCATION_FORMAT_FULL, ""), + CHECK(named(LLAgentUI::LOCATION_FORMAT_FULL, "") == std::string("Ye Olde Parcel, Hippotropolis (128, 64, 22)")); - ensure_equals("nor does an unnamed parcel", - unnamed(LLAgentUI::LOCATION_FORMAT_NO_COORDS, ""), + CHECK(unnamed(LLAgentUI::LOCATION_FORMAT_NO_COORDS, "") == std::string("Hippotropolis")); - ensure_equals("nor unnamed FULL", - unnamed(LLAgentUI::LOCATION_FORMAT_FULL, ""), + CHECK(unnamed(LLAgentUI::LOCATION_FORMAT_FULL, "") == std::string("Hippotropolis (128, 64, 22)")); } // Negative and multi-digit coordinates reach the string intact. The // position is signed, and a region's Z runs well past three digits. - template<> template<> - void object::test<4>() + TEST_CASE_METHOD(alagentlocationformat_data, "ALAgentLocationFormat: negative and multi-digit coordinates reach the string intact", "[ALAgentLocationFormat]") { ALAgentLocationFormat::format(out, LLAgentUI::LOCATION_FORMAT_FULL, "Parcel", "Region", "General", -4, 0, 4096); - ensure_equals("negative and large coordinates survive", - out, std::string("Parcel, Region (-4, 0, 4096) - General")); + CHECK(out == std::string("Parcel, Region (-4, 0, 4096) - General")); } // The buffer is cleared, not appended to. Callers keep it between rebuilds // for its capacity, and a formatter that appended would grow it forever. - template<> template<> - void object::test<5>() + TEST_CASE_METHOD(alagentlocationformat_data, "ALAgentLocationFormat: the buffer is cleared rather than appended to", "[ALAgentLocationFormat]") { out = "left over from last time"; - ensure_equals("the previous answer is gone", - named(LLAgentUI::LOCATION_FORMAT_NORMAL), + CHECK(named(LLAgentUI::LOCATION_FORMAT_NORMAL) == std::string("Ye Olde Parcel, Hippotropolis")); // Including on the assigning branches, which do not go through the // formatter's sink at all. out = "left over from last time"; - ensure_equals("including where the name is assigned whole", - named(LLAgentUI::LOCATION_FORMAT_LANDMARK), + UNSCOPED_INFO("including where the name is assigned whole"); + CHECK(named(LLAgentUI::LOCATION_FORMAT_LANDMARK) == std::string("Ye Olde Parcel")); } // A landmark name is cut at LANDMARK_NAME_MAX_BYTES, and on a character // boundary. The printf this replaced used "%.100s", which counts bytes and // will cut a multi-byte character in half. - template<> template<> - void object::test<6>() + TEST_CASE_METHOD(alagentlocationformat_data, "ALAgentLocationFormat: LANDMARK cuts a long name on a character boundary", "[ALAgentLocationFormat]") { // 33 three-byte characters is 99 bytes; a 34th would straddle the // hundredth. A byte-counting truncation keeps one byte of it. @@ -189,48 +164,47 @@ namespace tut { wide += "\xe6\x97\xa5"; // U+65E5 } - ensure_equals("the fixture is three bytes per character", - wide.size(), std::size_t(120)); + UNSCOPED_INFO("the fixture is three bytes per character"); + REQUIRE(wide.size() == std::size_t(120)); ALAgentLocationFormat::format(out, LLAgentUI::LOCATION_FORMAT_LANDMARK, wide, "Region", "", 0, 0, 0); - ensure_equals("cut on the character before the limit, not across it", - out.size(), std::size_t(99)); - ensure_equals("and it is whole characters", - out, wide.substr(0, 99)); + UNSCOPED_INFO("cut on the character before the limit, not across it"); + REQUIRE(out.size() == std::size_t(99)); + UNSCOPED_INFO("and it is whole characters"); + CHECK(out == wide.substr(0, 99)); // The region branch takes the same rule. ALAgentLocationFormat::format(out, LLAgentUI::LOCATION_FORMAT_LANDMARK, "", wide, "", 0, 0, 0); - ensure_equals("the unnamed-parcel branch truncates too", - out.size(), std::size_t(99)); + UNSCOPED_INFO("the unnamed-parcel branch truncates too"); + REQUIRE(out.size() == std::size_t(99)); } // A name shorter than the limit is not touched, and a name whose bytes // land exactly on it keeps all of them. - template<> template<> - void object::test<7>() + TEST_CASE_METHOD(alagentlocationformat_data, "ALAgentLocationFormat: a name at or under the byte limit is kept whole", "[ALAgentLocationFormat]") { ALAgentLocationFormat::format(out, LLAgentUI::LOCATION_FORMAT_LANDMARK, "Short", "Region", "", 0, 0, 0); - ensure_equals("a short name is left alone", out, std::string("Short")); + CHECK(out == std::string("Short")); const std::string exact(ALAgentLocationFormat::LANDMARK_NAME_MAX_BYTES, 'x'); ALAgentLocationFormat::format(out, LLAgentUI::LOCATION_FORMAT_LANDMARK, exact, "Region", "", 0, 0, 0); - ensure_equals("a name exactly at the limit keeps every byte", out, exact); + UNSCOPED_INFO("a name exactly at the limit keeps every byte"); + CHECK(out == exact); } // Only LANDMARK truncates. A long parcel name is shown in full everywhere // else, because everywhere else is a readout rather than an asset name. - template<> template<> - void object::test<8>() + TEST_CASE_METHOD(alagentlocationformat_data, "ALAgentLocationFormat: only LANDMARK truncates a long parcel name", "[ALAgentLocationFormat]") { const std::string long_name(200, 'y'); ALAgentLocationFormat::format(out, LLAgentUI::LOCATION_FORMAT_NORMAL, long_name, "Region", "", 0, 0, 0); - ensure_equals("NORMAL does not truncate", - out, long_name + ", Region"); + UNSCOPED_INFO("NORMAL does not truncate"); + CHECK(out == long_name + ", Region"); } } diff --git a/indra/newview/tests/alchattimestamp_test.cpp b/indra/newview/tests/alchattimestamp_test.cpp index 29a428df76..44f1590d9d 100644 --- a/indra/newview/tests/alchattimestamp_test.cpp +++ b/indra/newview/tests/alchattimestamp_test.cpp @@ -25,20 +25,11 @@ #include "linden_common.h" #include "../alchattimestamp.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { -struct alchattimestamp_data -{ -}; -typedef test_group alchattimestamp_group; -typedef alchattimestamp_group::object alchattimestamp_object; -alchattimestamp_group alchattimestamp_test("alchattimestamp"); - -template<> -template<> -void alchattimestamp_object::test<1>() +TEST_CASE("alchattimestamp: each date and time form is cut with its spacing as the prefix", "[alchattimestamp]") { for (const std::string date : { "", "2026/09/25 ", "2026/9/5 " }) { @@ -50,27 +41,26 @@ void alchattimestamp_object::test<1>() const std::string message = "Example Resident: text with a colon: and [14:05:09]"; const std::string line = prefix + message; const size_t length = ALChatTimestamp::prefixLength(line); - ensure_equals("timestamp recognized: " + prefix, length, prefix.size()); - ensure_equals("message preserved", line.substr(length), message); + CAPTURE(prefix); + REQUIRE(length == prefix.size()); + REQUIRE(line.substr(length) == message); } } } } -template<> -template<> -void alchattimestamp_object::test<2>() +TEST_CASE("alchattimestamp: a timestamp without seconds is left as it is", "[alchattimestamp]") { for (const std::string timestamp : { "14:05", "2:05 PM", "2026/09/25 14:05", "2026/09/25 2:05 pm", "" }) { - ensure_equals("missing seconds stay missing", ALChatTimestamp::format(timestamp, true), timestamp); - ensure_equals("minute display stays unchanged", ALChatTimestamp::format(timestamp, false), timestamp); + UNSCOPED_INFO("showing seconds invents none"); + REQUIRE(ALChatTimestamp::format(timestamp, true) == timestamp); + UNSCOPED_INFO("hiding seconds changes nothing"); + REQUIRE(ALChatTimestamp::format(timestamp, false) == timestamp); } } -template<> -template<> -void alchattimestamp_object::test<3>() +TEST_CASE("alchattimestamp: recorded seconds are shown or hidden without moving the minute", "[alchattimestamp]") { for (const std::string date : { "", "2026/09/25 " }) { @@ -81,25 +71,28 @@ void alchattimestamp_object::test<3>() { const std::string minutes = date + "2:05" + suffix; const std::string timestamp = date + "2:05" + seconds + suffix; - ensure_equals("recorded seconds retained", ALChatTimestamp::format(timestamp, true), timestamp); - ensure_equals("seconds hidden", ALChatTimestamp::format(timestamp, false), minutes); - ensure_equals("mixed logs share a merge minute", ALChatTimestamp::format(timestamp, false), + UNSCOPED_INFO("showing seconds keeps those recorded"); + REQUIRE(ALChatTimestamp::format(timestamp, true) == timestamp); + UNSCOPED_INFO("hiding seconds drops them"); + REQUIRE(ALChatTimestamp::format(timestamp, false) == minutes); + UNSCOPED_INFO("mixed logs share a merge minute"); + REQUIRE(ALChatTimestamp::format(timestamp, false) == ALChatTimestamp::format(minutes, false)); } } } } -template<> -template<> -void alchattimestamp_object::test<4>() +TEST_CASE("alchattimestamp: a malformed timestamp is neither cut nor rewritten", "[alchattimestamp]") { for (const std::string line : { "", "Example Resident: hello", "text [14:05:09]", "[14:05:9] hello", "[14:05:091] hello", "[14:05:09 hello" }) { - ensure_equals("not a timestamp prefix: " + line, ALChatTimestamp::prefixLength(line), size_t(0)); + CAPTURE(line); + REQUIRE(ALChatTimestamp::prefixLength(line) == size_t(0)); } - ensure_equals("unknown time is not rewritten", ALChatTimestamp::format("unknown:time:00", false), "unknown:time:00"); - ensure_equals("overlong seconds are not rewritten", ALChatTimestamp::format("14:05:091", false), "14:05:091"); + CHECK(ALChatTimestamp::format("unknown:time:00", false) == "unknown:time:00"); + UNSCOPED_INFO("overlong seconds are not rewritten"); + CHECK(ALChatTimestamp::format("14:05:091", false) == "14:05:091"); } -} // namespace tut +} // anonymous namespace diff --git a/indra/newview/tests/alcolorwheelmodel_test.cpp b/indra/newview/tests/alcolorwheelmodel_test.cpp index a024af121d..19d1c016a9 100644 --- a/indra/newview/tests/alcolorwheelmodel_test.cpp +++ b/indra/newview/tests/alcolorwheelmodel_test.cpp @@ -14,13 +14,14 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include +#include "altestmatchers.h" #include "../alcolorwheelmodel.h" #include -namespace tut +namespace { struct wheel_data { @@ -44,55 +45,55 @@ namespace tut } }; - typedef test_group wheel_group; - typedef wheel_group::object wheel_object; - tut::wheel_group wg("ALColorWheelModel"); - // --- the basis itself ---------------------------------------------------- // The whole design rests on {(1,1,1)/sqrt3, u, v} being orthonormal -- that // is what makes the decomposition lossless. A typo in the constants would // show up here and nowhere else obvious. - template<> template<> - void wheel_object::test<1>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: the chroma basis is orthonormal and has no grey in it", "[ALColorWheelModel]") { const LLVector3 u = ALColorWheelModel::chromaDirection(0.f); const LLVector3 v = ALColorWheelModel::chromaDirection(F_PI_BY_TWO); const LLVector3 grey(1.f, 1.f, 1.f); - ensure_approximately_equals("u is unit", u.magVec(), 1.f, 5); - ensure_approximately_equals("v is unit", v.magVec(), 1.f, 5); - ensure_approximately_equals("u and v are perpendicular", u * v, 0.f, 5); - ensure_approximately_equals("u has no achromatic part", u * grey, 0.f, 5); - ensure_approximately_equals("v has no achromatic part", v * grey, 0.f, 5); + CHECK_THAT(u.magVec(), ll_test::WithinBits(1.f, 5)); + CHECK_THAT(v.magVec(), ll_test::WithinBits(1.f, 5)); + UNSCOPED_INFO("u and v are perpendicular"); + CHECK_THAT(u * v, ll_test::WithinBits(0.f, 5)); + UNSCOPED_INFO("u has no achromatic part"); + CHECK_THAT(u * grey, ll_test::WithinBits(0.f, 5)); + UNSCOPED_INFO("v has no achromatic part"); + CHECK_THAT(v * grey, ll_test::WithinBits(0.f, 5)); } // Angle lands on the usual colour circle: red at 0, green at 120, blue at 240. - template<> template<> - void wheel_object::test<2>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: toPolar puts the primaries at their usual hues", "[ALColorWheelModel]") { F32 hue, sat; ALColorWheelModel::toPolar(LLVector3(1.f, 0.f, 0.f), hue, sat); - ensure_approximately_equals("pure red is hue 0", angleDelta(hue, deg(0.f)), 0.f, 4); + UNSCOPED_INFO("pure red is hue 0"); + CHECK_THAT(angleDelta(hue, deg(0.f)), ll_test::WithinBits(0.f, 4)); ALColorWheelModel::toPolar(LLVector3(0.f, 1.f, 0.f), hue, sat); - ensure_approximately_equals("pure green is 120", angleDelta(hue, deg(120.f)), 0.f, 4); + UNSCOPED_INFO("pure green is 120"); + CHECK_THAT(angleDelta(hue, deg(120.f)), ll_test::WithinBits(0.f, 4)); ALColorWheelModel::toPolar(LLVector3(0.f, 0.f, 1.f), hue, sat); - ensure_approximately_equals("pure blue is 240", angleDelta(hue, deg(240.f)), 0.f, 4); + UNSCOPED_INFO("pure blue is 240"); + CHECK_THAT(angleDelta(hue, deg(240.f)), ll_test::WithinBits(0.f, 4)); } // A neutral triplet has no chroma, and asking its hue must not produce a // NaN from atan2(0,0) -- the puck would jump the moment it crossed centre. - template<> template<> - void wheel_object::test<3>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: a grey has no saturation and a finite hue", "[ALColorWheelModel]") { F32 hue, sat; for (F32 grey : { -0.4f, 0.f, 0.5f, 1.f }) { + CAPTURE(grey); ALColorWheelModel::toPolar(LLVector3(grey, grey, grey), hue, sat); - ensure_approximately_equals("no saturation", sat, 0.f, 5); - ensure("hue is finite", std::isfinite(hue)); + REQUIRE_THAT(sat, ll_test::WithinBits(0.f, 5)); + REQUIRE(std::isfinite(hue)); } } @@ -101,8 +102,7 @@ namespace tut // The property everything else depends on: decompose and rebuild returns // the same triplet. If this fails, typing a number and dragging the puck // disagree about the value. - template<> template<> - void wheel_object::test<4>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: decomposing a triplet and rebuilding it returns the same triplet", "[ALColorWheelModel]") { const F32 samples[] = { -0.5f, -0.31f, -0.07f, 0.f, 0.12f, 0.29f, 0.5f }; for (F32 r : samples) @@ -111,34 +111,36 @@ namespace tut { for (F32 b : samples) { + CAPTURE(r, g, b); const LLVector3 in(r, g, b); F32 hue, sat; ALColorWheelModel::toPolar(in, hue, sat); const LLVector3 out = ALColorWheelModel::toRGB( ALColorWheelModel::masterOf(in), hue, sat); - ensure_approximately_equals("r round trips", out.mV[VX], in.mV[VX], 5); - ensure_approximately_equals("g round trips", out.mV[VY], in.mV[VY], 5); - ensure_approximately_equals("b round trips", out.mV[VZ], in.mV[VZ], 5); + REQUIRE_THAT(out.mV[VX], ll_test::WithinBits(in.mV[VX], 5)); + REQUIRE_THAT(out.mV[VY], ll_test::WithinBits(in.mV[VY], 5)); + REQUIRE_THAT(out.mV[VZ], ll_test::WithinBits(in.mV[VZ], 5)); } } } } // Round trip through the stored value, which is the path the widget takes. - template<> template<> - void wheel_object::test<5>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: setPolar round trips hue and saturation through the stored value", "[ALColorWheelModel]") { asLift(); for (S32 i = 0; i < 24; ++i) { + CAPTURE(i); const F32 hue = deg((F32)i * 15.f); const F32 sat = mWheel.getMaxSat() * 0.6f; mWheel.setPolar(hue, sat); - ensure_approximately_equals("hue survives", angleDelta(mWheel.getHue(), hue), 0.f, 4); - ensure_approximately_equals("saturation survives", mWheel.getSat(), sat, 4); - ensure_approximately_equals("master untouched", mWheel.getMaster(), 0.f, 5); + REQUIRE_THAT(angleDelta(mWheel.getHue(), hue), ll_test::WithinBits(0.f, 4)); + REQUIRE_THAT(mWheel.getSat(), ll_test::WithinBits(sat, 4)); + UNSCOPED_INFO("master untouched"); + REQUIRE_THAT(mWheel.getMaster(), ll_test::WithinBits(0.f, 5)); } } @@ -146,34 +148,34 @@ namespace tut // getMaxSat is the inradius, so a puck on the rim is reachable at EVERY // hue with the master centred -- nothing gets clamped. - template<> template<> - void wheel_object::test<6>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: a puck on the rim is reachable at every hue with the master centred", "[ALColorWheelModel]") { asLift(); const F32 rim = mWheel.getMaxSat(); for (S32 i = 0; i < 72; ++i) { + CAPTURE(i); const F32 hue = deg((F32)i * 5.f); mWheel.setPolar(hue, rim); - ensure_approximately_equals("rim is reachable at every hue", mWheel.getSat(), rim, 3); - ensure_approximately_equals("and the hue is the one asked for", - angleDelta(mWheel.getHue(), hue), 0.f, 3); + REQUIRE_THAT(mWheel.getSat(), ll_test::WithinBits(rim, 3)); + REQUIRE_THAT(angleDelta(mWheel.getHue(), hue), ll_test::WithinBits(0.f, 3)); } } // The inradius is sqrt(1.5) * halfRange, and the tightest directions are // the primaries, where one channel lands exactly on its limit. - template<> template<> - void wheel_object::test<7>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: getMaxSat is the inradius and red on the rim sits on its limit", "[ALColorWheelModel]") { asLift(); - ensure_approximately_equals("inradius", mWheel.getMaxSat(), sqrtf(1.5f) * 0.5f, 4); + CHECK_THAT(mWheel.getMaxSat(), ll_test::WithinBits(sqrtf(1.5f) * 0.5f, 4)); mWheel.setPolar(0.f, mWheel.getMaxSat()); - ensure_approximately_equals("red sits on its limit", mWheel.getRGB().mV[VX], 0.5f, 3); + UNSCOPED_INFO("red sits on its limit"); + CHECK_THAT(mWheel.getRGB().mV[VX], ll_test::WithinBits(0.5f, 3)); asGain(); - ensure_approximately_equals("gain inradius", mWheel.getMaxSat(), sqrtf(1.5f) * 0.5f, 4); + UNSCOPED_INFO("gain inradius"); + CHECK_THAT(mWheel.getMaxSat(), ll_test::WithinBits(sqrtf(1.5f) * 0.5f, 4)); } // Past the rim the channel clamp bites: the value stays legal, and the @@ -194,8 +196,7 @@ namespace tut // Clamping moves the mean, so the result leaves the master-pinned hexagon // and lands on the projection of the whole cube -- the third figure. A // bound of the second is too tight and fails here. - template<> template<> - void wheel_object::test<8>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: past the rim the channel clamp keeps the value legal", "[ALColorWheelModel]") { asLift(); const F32 rim = mWheel.getMaxSat(); @@ -203,38 +204,45 @@ namespace tut for (S32 i = 0; i < 36; ++i) { + CAPTURE(i); const F32 hue = deg((F32)i * 10.f); mWheel.setPolar(hue, rim * 4.f); const LLVector3& rgb = mWheel.getRGB(); for (S32 c = 0; c < 3; ++c) { - ensure("channel stayed in range", rgb.mV[c] >= -0.5f - 1e-5f && rgb.mV[c] <= 0.5f + 1e-5f); + CAPTURE(c); + REQUIRE(rgb.mV[c] >= -0.5f - 1e-5f); + REQUIRE(rgb.mV[c] <= 0.5f + 1e-5f); } - ensure("saturation did not run away", mWheel.getSat() <= widest + 1e-4f); - ensure("but it did move out to the boundary", mWheel.getSat() > rim * 0.9f); + UNSCOPED_INFO("saturation did not run away"); + REQUIRE(mWheel.getSat() <= widest + 1e-4f); + UNSCOPED_INFO("but it did move out to the boundary"); + REQUIRE(mWheel.getSat() > rim * 0.9f); } // Far enough past the rim in a primary direction and the triplet is // pinned to a cube corner, which is exactly the widest case. mWheel.setPolar(0.f, rim * 4.f); - ensure_approximately_equals("corner-pinned saturation", mWheel.getSat(), widest, 4); - ensure_approximately_equals("r on its ceiling", mWheel.getRGB().mV[VX], 0.5f, 5); - ensure_approximately_equals("g on its floor", mWheel.getRGB().mV[VY], -0.5f, 5); + UNSCOPED_INFO("corner-pinned saturation"); + CHECK_THAT(mWheel.getSat(), ll_test::WithinBits(widest, 4)); + UNSCOPED_INFO("r on its ceiling"); + CHECK_THAT(mWheel.getRGB().mV[VX], ll_test::WithinBits(0.5f, 5)); + UNSCOPED_INFO("g on its floor"); + CHECK_THAT(mWheel.getRGB().mV[VY], ll_test::WithinBits(-0.5f, 5)); } // Clamping must not silently rotate the hue -- the puck would slide around // the ring while the user dragged straight outward. - template<> template<> - void wheel_object::test<9>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: clamping past the rim does not rotate the hue", "[ALColorWheelModel]") { asLift(); for (S32 i = 0; i < 12; ++i) { + CAPTURE(i); const F32 hue = deg((F32)i * 30.f); mWheel.setPolar(hue, mWheel.getMaxSat() * 3.f); - ensure_approximately_equals("hue held through the clamp", - angleDelta(mWheel.getHue(), hue), 0.f, 3); + REQUIRE_THAT(angleDelta(mWheel.getHue(), hue), ll_test::WithinBits(0.f, 3)); } } @@ -242,8 +250,7 @@ namespace tut // Moving the master keeps the chroma, which is what makes the wheel and // its slider feel independent. - template<> template<> - void wheel_object::test<10>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: moving the master keeps the hue and saturation", "[ALColorWheelModel]") { asGain(); mWheel.setPolar(deg(200.f), mWheel.getMaxSat() * 0.4f); @@ -251,40 +258,40 @@ namespace tut const F32 sat = mWheel.getSat(); mWheel.setMaster(1.2f); - ensure_approximately_equals("master moved", mWheel.getMaster(), 1.2f, 4); - ensure_approximately_equals("hue kept", angleDelta(mWheel.getHue(), hue), 0.f, 3); - ensure_approximately_equals("saturation kept", mWheel.getSat(), sat, 3); + CHECK_THAT(mWheel.getMaster(), ll_test::WithinBits(1.2f, 4)); + CHECK_THAT(angleDelta(mWheel.getHue(), hue), ll_test::WithinBits(0.f, 3)); + CHECK_THAT(mWheel.getSat(), ll_test::WithinBits(sat, 3)); } - // A master pushed past its range clamps, and the triplet stays legal. - template<> template<> - void wheel_object::test<11>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: a master pushed past its range clamps and the triplet stays legal", "[ALColorWheelModel]") { asGain(); mWheel.setMaster(9.f); - ensure("master clamped", mWheel.getMaster() <= 1.5f + 1e-5f); + CHECK(mWheel.getMaster() <= 1.5f + 1e-5f); for (S32 c = 0; c < 3; ++c) { - ensure("channel in range", mWheel.getRGB().mV[c] <= 1.5f + 1e-5f); + CAPTURE(c); + REQUIRE(mWheel.getRGB().mV[c] <= 1.5f + 1e-5f); } } // Tint wheels lock the master, because the renderer divides each tint by // dot(tint, LUMA) -- magnitude cancels, so a master there would be a // control that visibly does nothing. - template<> template<> - void wheel_object::test<12>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: a tint wheel ignores the master and re-centres it on a puck move", "[ALColorWheelModel]") { asTint(); mWheel.setPolar(deg(90.f), mWheel.getMaxSat() * 0.5f); const F32 master = mWheel.getMaster(); mWheel.setMaster(0.9f); - ensure_approximately_equals("master ignored", mWheel.getMaster(), master, 5); + UNSCOPED_INFO("master ignored"); + CHECK_THAT(mWheel.getMaster(), ll_test::WithinBits(master, 5)); // ... and a fresh puck move re-centres on the configured neutral. mWheel.setPolar(deg(30.f), mWheel.getMaxSat() * 0.5f); - ensure_approximately_equals("re-centred on neutral", mWheel.getMaster(), 0.5f, 4); + UNSCOPED_INFO("re-centred on neutral"); + CHECK_THAT(mWheel.getMaster(), ll_test::WithinBits(0.5f, 4)); } // --- ring / puck agreement ---------------------------------------------- @@ -292,34 +299,33 @@ namespace tut // The ring is generated from the same basis as the puck, so the colour // shown at an angle is the colour dragging to that angle produces. Drawing // a generic HSV ring instead is where these two drift apart. - template<> template<> - void wheel_object::test<13>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: the ring's colour at a hue is coloured and has that hue", "[ALColorWheelModel]") { for (S32 i = 0; i < 36; ++i) { + CAPTURE(i); const F32 hue = deg((F32)i * 10.f); const LLVector3 ring = ALColorWheelModel::ringColor(hue); F32 ring_hue, ring_sat; ALColorWheelModel::toPolar(ring, ring_hue, ring_sat); - ensure_approximately_equals("ring hue matches the puck's", - angleDelta(ring_hue, hue), 0.f, 3); - ensure("ring is actually coloured", ring_sat > 0.f); + REQUIRE_THAT(angleDelta(ring_hue, hue), ll_test::WithinBits(0.f, 3)); + REQUIRE(ring_sat > 0.f); } } // The ring's reference chroma is chosen to stay inside 0..1, so no channel // is ever clipped -- clipping would bend the hue it is advertising. - template<> template<> - void wheel_object::test<14>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: every ring channel stays strictly inside 0..1", "[ALColorWheelModel]") { for (S32 i = 0; i < 72; ++i) { const LLVector3 ring = ALColorWheelModel::ringColor(deg((F32)i * 5.f)); for (S32 c = 0; c < 3; ++c) { - ensure("ring channel is strictly inside the gamut", - ring.mV[c] > 0.001f && ring.mV[c] < 0.999f); + CAPTURE(i, c); + REQUIRE(ring.mV[c] > 0.001f); + REQUIRE(ring.mV[c] < 0.999f); } } } @@ -327,93 +333,102 @@ namespace tut // --- numeric entry and reset -------------------------------------------- // Typing into one field leaves the other two alone and moves the puck. - template<> template<> - void wheel_object::test<15>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: setChannel writes one channel alone and moves the puck", "[ALColorWheelModel]") { asLift(); mWheel.setChannel(0, 0.2f); - ensure_approximately_equals("channel written", mWheel.getRGB().mV[VX], 0.2f, 5); - ensure_approximately_equals("g untouched", mWheel.getRGB().mV[VY], 0.f, 5); - ensure_approximately_equals("b untouched", mWheel.getRGB().mV[VZ], 0.f, 5); - ensure("puck moved off centre", mWheel.getSat() > 0.f); - ensure_approximately_equals("towards red", angleDelta(mWheel.getHue(), 0.f), 0.f, 3); + CHECK_THAT(mWheel.getRGB().mV[VX], ll_test::WithinBits(0.2f, 5)); + UNSCOPED_INFO("g untouched"); + CHECK_THAT(mWheel.getRGB().mV[VY], ll_test::WithinBits(0.f, 5)); + UNSCOPED_INFO("b untouched"); + CHECK_THAT(mWheel.getRGB().mV[VZ], ll_test::WithinBits(0.f, 5)); + UNSCOPED_INFO("puck moved off centre"); + CHECK(mWheel.getSat() > 0.f); + UNSCOPED_INFO("towards red"); + CHECK_THAT(angleDelta(mWheel.getHue(), 0.f), ll_test::WithinBits(0.f, 3)); mWheel.setChannel(0, 9.f); - ensure_approximately_equals("typed value clamped", mWheel.getRGB().mV[VX], 0.5f, 5); + UNSCOPED_INFO("typed value clamped"); + CHECK_THAT(mWheel.getRGB().mV[VX], ll_test::WithinBits(0.5f, 5)); mWheel.setChannel(7, 1.f); // out of range, ignored - ensure_approximately_equals("bad index ignored", mWheel.getRGB().mV[VX], 0.5f, 5); + UNSCOPED_INFO("bad index ignored"); + CHECK_THAT(mWheel.getRGB().mV[VX], ll_test::WithinBits(0.5f, 5)); } - // Reset returns the configured neutral for each flavour. - template<> template<> - void wheel_object::test<16>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: reset returns the configured neutral for each flavour", "[ALColorWheelModel]") { asLift(); mWheel.setPolar(deg(45.f), mWheel.getMaxSat()); mWheel.reset(); - ensure_approximately_equals("lift neutral is 0", mWheel.getMaster(), 0.f, 5); - ensure_approximately_equals("and colourless", mWheel.getSat(), 0.f, 5); + UNSCOPED_INFO("lift neutral is 0"); + CHECK_THAT(mWheel.getMaster(), ll_test::WithinBits(0.f, 5)); + UNSCOPED_INFO("and colourless"); + CHECK_THAT(mWheel.getSat(), ll_test::WithinBits(0.f, 5)); asGain(); mWheel.reset(); - ensure_approximately_equals("gain neutral is 1", mWheel.getMaster(), 1.f, 5); + UNSCOPED_INFO("gain neutral is 1"); + CHECK_THAT(mWheel.getMaster(), ll_test::WithinBits(1.f, 5)); asTint(); mWheel.reset(); - ensure_approximately_equals("tint neutral is 0.5", mWheel.getMaster(), 0.5f, 5); - ensure_approximately_equals("tint neutral r", mWheel.getRGB().mV[VX], 0.5f, 5); + UNSCOPED_INFO("tint neutral is 0.5"); + CHECK_THAT(mWheel.getMaster(), ll_test::WithinBits(0.5f, 5)); + UNSCOPED_INFO("tint neutral r"); + CHECK_THAT(mWheel.getRGB().mV[VX], ll_test::WithinBits(0.5f, 5)); } // Re-configuring re-clamps whatever was already held, so switching a wheel // from lift to gain cannot leave an out-of-range value behind. - template<> template<> - void wheel_object::test<17>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: configure re-clamps the value already held", "[ALColorWheelModel]") { asLift(); mWheel.setRGB(LLVector3(-0.4f, 0.f, 0.3f)); asGain(); for (S32 c = 0; c < 3; ++c) { - ensure("re-clamped on configure", - mWheel.getRGB().mV[c] >= 0.5f - 1e-5f && mWheel.getRGB().mV[c] <= 1.5f + 1e-5f); + CAPTURE(c); + REQUIRE(mWheel.getRGB().mV[c] >= 0.5f - 1e-5f); + REQUIRE(mWheel.getRGB().mV[c] <= 1.5f + 1e-5f); } } - // A reversed range is accepted rather than producing a negative width. - template<> template<> - void wheel_object::test<18>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: a reversed range is accepted rather than producing a negative width", "[ALColorWheelModel]") { mWheel.configure(0.f, 0.5f, -0.5f, false); - ensure_approximately_equals("min is the low one", mWheel.getMin(), -0.5f, 5); - ensure_approximately_equals("max is the high one", mWheel.getMax(), 0.5f, 5); - ensure("max saturation is positive", mWheel.getMaxSat() > 0.f); + UNSCOPED_INFO("min is the low one"); + CHECK_THAT(mWheel.getMin(), ll_test::WithinBits(-0.5f, 5)); + UNSCOPED_INFO("max is the high one"); + CHECK_THAT(mWheel.getMax(), ll_test::WithinBits(0.5f, 5)); + CHECK(mWheel.getMaxSat() > 0.f); } // Exactly zero, not approximately: the vectorscope bins chroma with floorf, // so an epsilon of either sign puts one grey in a different cell from the // next. Compared bit for bit because that is the property callers rely on, // and because an epsilon is exactly what a tolerance would hide. - template<> template<> - void wheel_object::test<19>() + TEST_CASE_METHOD(wheel_data, "ALColorWheelModel: toChroma gives a grey exactly zero chroma", "[ALColorWheelModel]") { for (S32 level = 0; level < 256; ++level) { + CAPTURE(level); const F32 grey = (F32)level / 255.f; F32 u = 1.f, v = 1.f; ALColorWheelModel::toChroma(LLVector3(grey, grey, grey), u, v); - ensure("grey has no u", u == 0.f); - ensure("grey has no v", v == 0.f); + REQUIRE(u == 0.f); + REQUIRE(v == 0.f); } // Outside 0..1 as well: wheels carry lift and gain, which sit either // side of the unit range. for (F32 grey : { -0.4f, 1.5f, 37.f }) { + CAPTURE(grey); F32 u = 1.f, v = 1.f; ALColorWheelModel::toChroma(LLVector3(grey, grey, grey), u, v); - ensure("grey has no u", u == 0.f); - ensure("grey has no v", v == 0.f); + REQUIRE(u == 0.f); + REQUIRE(v == 0.f); } } } diff --git a/indra/newview/tests/alcrashreporter_test.cpp b/indra/newview/tests/alcrashreporter_test.cpp index 0c06ff7690..ba248bff05 100644 --- a/indra/newview/tests/alcrashreporter_test.cpp +++ b/indra/newview/tests/alcrashreporter_test.cpp @@ -30,56 +30,39 @@ #include "lluuid.h" #include "v3math.h" -#include "../test/lltut.h" +#include #include -namespace tut +namespace { - struct alcrashreporter_data + TEST_CASE("alcrashreporter: the release is the package at a version plus a build", "[alcrashreporter]") { - }; - - typedef test_group alcrashreporter_group; - typedef alcrashreporter_group::object object; - alcrashreporter_group alcrashreportergrp("alcrashreporter"); - - template<> template<> - void object::test<1>() - { - set_test_name("the release is the package at a version plus a build"); - ensure_equals(ALCrashReporter::releaseName(26, 4, 0, 64033), "alchemy@26.4.0+64033"); + CHECK(ALCrashReporter::releaseName(26, 4, 0, 64033) == "alchemy@26.4.0+64033"); } - template<> template<> - void object::test<2>() + TEST_CASE("alcrashreporter: a location is the region and whole metres", "[alcrashreporter]") { - set_test_name("a location is the region and whole metres"); - ensure_equals(ALCrashReporter::locationTag("Hippotropolis", LLVector3(128.4f, 63.6f, 22.5f)), + CHECK(ALCrashReporter::locationTag("Hippotropolis", LLVector3(128.4f, 63.6f, 22.5f)) == "Hippotropolis/128/64/23"); } - template<> template<> - void object::test<3>() + TEST_CASE("alcrashreporter: a location keeps the region's own spelling", "[alcrashreporter]") { - set_test_name("a location keeps the region's own spelling"); - ensure_equals(ALCrashReporter::locationTag("Ye Olde Region", LLVector3(0.f, 0.f, 0.f)), + CHECK(ALCrashReporter::locationTag("Ye Olde Region", LLVector3(0.f, 0.f, 0.f)) == "Ye Olde Region/0/0/0"); } - template<> template<> - void object::test<4>() + TEST_CASE("alcrashreporter: the run id is one string for the whole run", "[alcrashreporter]") { - set_test_name("the run id is one string for the whole run"); const std::string& first = ALCrashReporter::runId(); - ensure_equals("a UUID", first.size(), 36u); - ensure_equals("stable", ALCrashReporter::runId(), first); + UNSCOPED_INFO("a UUID"); + CHECK(first.size() == 36u); + CHECK(ALCrashReporter::runId() == first); } - template<> template<> - void object::test<5>() + TEST_CASE("alcrashreporter: the previous run is read from its static debug file", "[alcrashreporter]") { - set_test_name("the previous run is read from its static debug file"); LLSD info; info["RunId"] = "6f4b1b2e-9d8c-4d0f-a5b7-2c3e4f5a6b7c"; info["SLLog"] = "/logs/Alchemy.crash"; @@ -90,147 +73,149 @@ namespace tut info["FatalMessage"] = "LL_ERRS: something"; ALCrashReporter::PreviousRun run = ALCrashReporter::previousRun(info); - ensure_equals(run.runId, "6f4b1b2e-9d8c-4d0f-a5b7-2c3e4f5a6b7c"); - ensure_equals(run.logFile, "/logs/Alchemy.crash"); - ensure_equals(run.userSettingsFile, "/settings/settings.xml"); - ensure_equals(run.accountSettingsFile, "/settings/ye_olde/settings_per_account.xml"); - ensure_equals("underscores become spaces", run.agentName, "Ye Olde Avatar"); - ensure_equals(run.region, "Hippotropolis"); - ensure_equals(run.fatalMessage, "LL_ERRS: something"); + CHECK(run.runId == "6f4b1b2e-9d8c-4d0f-a5b7-2c3e4f5a6b7c"); + CHECK(run.logFile == "/logs/Alchemy.crash"); + CHECK(run.userSettingsFile == "/settings/settings.xml"); + CHECK(run.accountSettingsFile == "/settings/ye_olde/settings_per_account.xml"); + UNSCOPED_INFO("underscores become spaces"); + CHECK(run.agentName == "Ye Olde Avatar"); + CHECK(run.region == "Hippotropolis"); + CHECK(run.fatalMessage == "LL_ERRS: something"); } - template<> template<> - void object::test<6>() + TEST_CASE("alcrashreporter: a file from before run ids reads as empty fields", "[alcrashreporter]") { - set_test_name("a file from before run ids reads as empty fields"); ALCrashReporter::PreviousRun run = ALCrashReporter::previousRun(LLSD()); - ensure("no run id", run.runId.empty()); - ensure("no log", run.logFile.empty()); - ensure("no name", run.agentName.empty()); + CHECK(run.runId.empty()); + CHECK(run.logFile.empty()); + CHECK(run.agentName.empty()); } - template<> template<> - void object::test<7>() + TEST_CASE("alcrashreporter: consent is a sentinel file that allowing makes and withdrawing removes", "[alcrashreporter]") { - set_test_name("consent is a sentinel file: recorded, seen, withdrawn"); // A name of this run's own: another run of the tests may be at it. const std::string sentinel = (std::filesystem::temp_directory_path() / ("alcrashreporter_test_consent_" + LLUUID::generateNewID().asString())).string(); ALCrashReporter::recordConsent(sentinel, false); - ensure("absent to begin with", !ALCrashReporter::consentRecorded(sentinel)); + UNSCOPED_INFO("absent to begin with"); + CHECK_FALSE(ALCrashReporter::consentRecorded(sentinel)); ALCrashReporter::recordConsent(sentinel, true); - ensure("present once allowed", ALCrashReporter::consentRecorded(sentinel)); + UNSCOPED_INFO("present once allowed"); + CHECK(ALCrashReporter::consentRecorded(sentinel)); ALCrashReporter::recordConsent(sentinel, true); - ensure("allowing twice keeps it", ALCrashReporter::consentRecorded(sentinel)); + UNSCOPED_INFO("allowing twice keeps it"); + CHECK(ALCrashReporter::consentRecorded(sentinel)); ALCrashReporter::recordConsent(sentinel, false); - ensure("gone once withdrawn", !ALCrashReporter::consentRecorded(sentinel)); + UNSCOPED_INFO("gone once withdrawn"); + CHECK_FALSE(ALCrashReporter::consentRecorded(sentinel)); ALCrashReporter::recordConsent(sentinel, false); - ensure("withdrawing twice is quiet", !ALCrashReporter::consentRecorded(sentinel)); + UNSCOPED_INFO("withdrawing twice is quiet"); + CHECK_FALSE(ALCrashReporter::consentRecorded(sentinel)); } - template<> template<> - void object::test<8>() + TEST_CASE("alcrashreporter: a location written into a buffer is the same or cut to fit", "[alcrashreporter]") { - set_test_name("a location written into a buffer is the same, or cut to fit"); const LLVector3 position(128.4f, -63.6f, 22.5f); char buffer[64]; size_t length = ALCrashReporter::locationTag(buffer, sizeof(buffer), "Hippotropolis", position); - ensure_equals("whole", std::string(buffer, length), "Hippotropolis/128/-64/23"); - ensure_equals("terminated", buffer[length], '\0'); + CHECK(std::string(buffer, length) == "Hippotropolis/128/-64/23"); + CHECK(buffer[length] == '\0'); char small[12]; length = ALCrashReporter::locationTag(small, sizeof(small), "Hippotropolis", position); - ensure_equals("cut", std::string(small, length), "Hippotropol"); - ensure_equals("cut and terminated", small[length], '\0'); + CHECK(std::string(small, length) == "Hippotropol"); + CHECK(small[length] == '\0'); length = ALCrashReporter::locationTag(small, sizeof(small), "Ahern", position); - ensure_equals("cut in the numbers", std::string(small, length), "Ahern/128/-"); + UNSCOPED_INFO("cut in the numbers"); + CHECK(std::string(small, length) == "Ahern/128/-"); } - template<> template<> - void object::test<9>() + TEST_CASE("alcrashreporter: an event id is its 32 hex digits however it is spelled", "[alcrashreporter]") { - set_test_name("an event id is its 32 hex digits however it is spelled"); - ensure_equals("dashed", ALCrashReporter::compactEventId("0123ABCD-89ab-cdef-0123-456789ABCDEF"), + CHECK(ALCrashReporter::compactEventId("0123ABCD-89ab-cdef-0123-456789ABCDEF") == "0123abcd89abcdef0123456789abcdef"); - ensure_equals("braced", ALCrashReporter::compactEventId("{0123abcd-89ab-cdef-0123-456789abcdef}"), + CHECK(ALCrashReporter::compactEventId("{0123abcd-89ab-cdef-0123-456789abcdef}") == "0123abcd89abcdef0123456789abcdef"); - ensure_equals("already compact", ALCrashReporter::compactEventId("0123abcd89abcdef0123456789abcdef"), + CHECK(ALCrashReporter::compactEventId("0123abcd89abcdef0123456789abcdef") == "0123abcd89abcdef0123456789abcdef"); - ensure("too short", ALCrashReporter::compactEventId("0123abcd").empty()); - ensure("not hex", ALCrashReporter::compactEventId("0123abcd89abcdef0123456789abcdeg").empty()); - ensure("empty", ALCrashReporter::compactEventId("").empty()); + UNSCOPED_INFO("too short"); + CHECK(ALCrashReporter::compactEventId("0123abcd").empty()); + UNSCOPED_INFO("not hex"); + CHECK(ALCrashReporter::compactEventId("0123abcd89abcdef0123456789abcdeg").empty()); + CHECK(ALCrashReporter::compactEventId("").empty()); } - template<> template<> - void object::test<10>() + TEST_CASE("alcrashreporter: a freeze's report is recorded for the next run to take once", "[alcrashreporter]") { - set_test_name("a freeze's report is recorded for the next run, which takes it once"); // Names of this run's own, here and below: another run of the tests may be at it. const std::string record = (std::filesystem::temp_directory_path() / ("alcrashreporter_test_last_report_" + LLUUID::generateNewID().asString())).string(); ALCrashReporter::takeRecordedReport(record); - ensure("nothing to begin with", !ALCrashReporter::takeRecordedReport(record)); + UNSCOPED_INFO("nothing to begin with"); + CHECK_FALSE(ALCrashReporter::takeRecordedReport(record)); ALCrashReporter::recordReport(record, { "freeze", "0123abcd89abcdef0123456789abcdef" }); const auto taken = ALCrashReporter::takeRecordedReport(record); - ensure("taken", taken.has_value()); - ensure_equals("kind", taken->kind, "freeze"); - ensure_equals("id", taken->eventId, "0123abcd89abcdef0123456789abcdef"); - ensure("once", !ALCrashReporter::takeRecordedReport(record)); + REQUIRE(taken.has_value()); + CHECK(taken->kind == "freeze"); + CHECK(taken->eventId == "0123abcd89abcdef0123456789abcdef"); + UNSCOPED_INFO("taken only once"); + CHECK_FALSE(ALCrashReporter::takeRecordedReport(record)); ALCrashReporter::recordReport(record, { "lunch", "0123abcd89abcdef0123456789abcdef" }); - ensure("an unknown kind is not a report", !ALCrashReporter::takeRecordedReport(record)); + UNSCOPED_INFO("an unknown kind is not a report"); + CHECK_FALSE(ALCrashReporter::takeRecordedReport(record)); ALCrashReporter::recordReport(record, { "crash", "nonsense" }); - ensure("nor is a broken id", !ALCrashReporter::takeRecordedReport(record)); + UNSCOPED_INFO("nor is a broken id"); + CHECK_FALSE(ALCrashReporter::takeRecordedReport(record)); } - template<> template<> - void object::test<11>() + TEST_CASE("alcrashreporter: the nil UUID is no event however it is spelled", "[alcrashreporter]") { - set_test_name("the nil UUID is no event, however it is spelled"); - ensure("dashed", ALCrashReporter::compactEventId("00000000-0000-0000-0000-000000000000").empty()); - ensure("braced", ALCrashReporter::compactEventId("{00000000-0000-0000-0000-000000000000}").empty()); - ensure("compact", ALCrashReporter::compactEventId("00000000000000000000000000000000").empty()); - ensure_equals("one digit set is an event", - ALCrashReporter::compactEventId("00000000-0000-0000-0000-000000000001"), + CHECK(ALCrashReporter::compactEventId("00000000-0000-0000-0000-000000000000").empty()); + CHECK(ALCrashReporter::compactEventId("{00000000-0000-0000-0000-000000000000}").empty()); + CHECK(ALCrashReporter::compactEventId("00000000000000000000000000000000").empty()); + UNSCOPED_INFO("one digit set is an event"); + CHECK(ALCrashReporter::compactEventId("00000000-0000-0000-0000-000000000001") == "00000000000000000000000000000001"); const std::string record = (std::filesystem::temp_directory_path() / ("alcrashreporter_test_nil_report_" + LLUUID::generateNewID().asString())).string(); ALCrashReporter::recordReport(record, { "freeze", "00000000000000000000000000000000" }); - ensure("a nil report is not taken", !ALCrashReporter::takeRecordedReport(record)); + UNSCOPED_INFO("a nil report is not taken"); + CHECK_FALSE(ALCrashReporter::takeRecordedReport(record)); } - template<> template<> - void object::test<12>() + TEST_CASE("alcrashreporter: a crash found at start is the previous run's only if it carries its id", "[alcrashreporter]") { - set_test_name("a crash found at start is the previous run's only when it carries that run's id"); const std::string_view event = "0123abcd-89ab-cdef-0123-456789abcdef"; const std::string_view previous = "6f4b1b2e-9d8c-4d0f-a5b7-2c3e4f5a6b7c"; const auto crash = ALCrashReporter::previousRunCrash(event, previous, previous); - ensure("the previous run's", crash.has_value()); - ensure_equals("a crash", crash->kind, "crash"); - ensure_equals("its id", crash->eventId, "0123abcd89abcdef0123456789abcdef"); - ensure("the run id however it is spelled", - ALCrashReporter::previousRunCrash(event, "6F4B1B2E9D8C4D0FA5B72C3E4F5A6B7C", previous).has_value()); - - ensure("another run's", !ALCrashReporter::previousRunCrash(event, "7f4b1b2e-9d8c-4d0f-a5b7-2c3e4f5a6b7c", + UNSCOPED_INFO("the previous run's"); + REQUIRE(crash.has_value()); + CHECK(crash->kind == "crash"); + CHECK(crash->eventId == "0123abcd89abcdef0123456789abcdef"); + UNSCOPED_INFO("the run id however it is spelled"); + CHECK(ALCrashReporter::previousRunCrash(event, "6F4B1B2E9D8C4D0FA5B72C3E4F5A6B7C", previous).has_value()); + + UNSCOPED_INFO("another run's"); + CHECK_FALSE(ALCrashReporter::previousRunCrash(event, "7f4b1b2e-9d8c-4d0f-a5b7-2c3e4f5a6b7c", previous)); - ensure("one that carries no run id", !ALCrashReporter::previousRunCrash(event, "", previous)); - ensure("any, when the previous run is not known", !ALCrashReporter::previousRunCrash(event, previous, "")); - ensure("one filed as nothing", - !ALCrashReporter::previousRunCrash("00000000-0000-0000-0000-000000000000", previous, previous)); + UNSCOPED_INFO("one that carries no run id"); + CHECK_FALSE(ALCrashReporter::previousRunCrash(event, "", previous)); + UNSCOPED_INFO("any when the previous run is not known"); + CHECK_FALSE(ALCrashReporter::previousRunCrash(event, previous, "")); + UNSCOPED_INFO("one filed as nothing"); + CHECK_FALSE(ALCrashReporter::previousRunCrash("00000000-0000-0000-0000-000000000000", previous, previous)); } - template<> template<> - void object::test<13>() + TEST_CASE("alcrashreporter: a record replaces the last whole and leaves nothing beside it", "[alcrashreporter]") { - set_test_name("a record is written whole over the last, and nothing is left beside it"); const std::filesystem::path dir = std::filesystem::temp_directory_path() / ("alcrashreporter_test_records_" + LLUUID::generateNewID().asString()); std::filesystem::create_directories(dir); @@ -242,19 +227,25 @@ namespace tut ALCrashReporter::recordReport(record, { "freeze", "0123abcd89abcdef0123456789abcdef" }); ALCrashReporter::recordReport(record, { "crash", "fedcba9876543210fedcba9876543210" }); - ensure_equals("the record alone", entries(), 1); + UNSCOPED_INFO("the record alone"); + CHECK(entries() == 1); const auto taken = ALCrashReporter::takeRecordedReport(record); - ensure("taken", taken.has_value()); - ensure_equals("the later kind", taken->kind, "crash"); - ensure_equals("the later id", taken->eventId, "fedcba9876543210fedcba9876543210"); - ensure_equals("nothing once taken", entries(), 0); + REQUIRE(taken.has_value()); + UNSCOPED_INFO("the later kind"); + CHECK(taken->kind == "crash"); + UNSCOPED_INFO("the later id"); + CHECK(taken->eventId == "fedcba9876543210fedcba9876543210"); + UNSCOPED_INFO("nothing once taken"); + CHECK(entries() == 0); // A record that cannot be put in place leaves nothing written. const std::filesystem::path blocked = dir / "blocked"; std::filesystem::create_directories(blocked / "inside"); ALCrashReporter::recordReport(blocked.string(), { "freeze", "0123abcd89abcdef0123456789abcdef" }); - ensure_equals("only what was there", entries(), 1); - ensure("still a directory", std::filesystem::is_directory(blocked)); + UNSCOPED_INFO("only what was there"); + CHECK(entries() == 1); + UNSCOPED_INFO("still a directory"); + CHECK(std::filesystem::is_directory(blocked)); std::filesystem::remove_all(dir); } diff --git a/indra/newview/tests/alcurvemodel_test.cpp b/indra/newview/tests/alcurvemodel_test.cpp index 558c76c7c3..dbabd9c9ad 100644 --- a/indra/newview/tests/alcurvemodel_test.cpp +++ b/indra/newview/tests/alcurvemodel_test.cpp @@ -14,7 +14,8 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include +#include "altestmatchers.h" #include "../alcurvemodel.h" @@ -22,7 +23,7 @@ #include #include -namespace tut +namespace { struct curve_data { @@ -100,119 +101,125 @@ namespace tut } }; - typedef test_group curve_group; - typedef curve_group::object curve_object; - tut::curve_group cg("ALCurveModel"); - // --- spline: ordering and point management ------------------------------- - // A fresh model is the identity ramp. - template<> template<> - void curve_object::test<1>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: a fresh model is the identity ramp", "[ALCurveModel]") { - ensure_equals("two points by default", mCurve.getPointCount(), 2); + CHECK(mCurve.getPointCount() == 2); for (S32 i = 0; i <= 10; ++i) { const F32 x = i * 0.1f; - ensure_approximately_equals("identity ramp", mCurve.evaluate(x), x, 5); + CAPTURE(x); + REQUIRE_THAT(mCurve.evaluate(x), ll_test::WithinBits(x, 5)); } } // Points added out of order come back sorted by x, and the reported index // is where the point actually landed. - template<> template<> - void curve_object::test<2>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: points added out of order come back sorted by x", "[ALCurveModel]") { const S32 idx_hi = mCurve.addPoint(0.8f, 0.9f); const S32 idx_lo = mCurve.addPoint(0.2f, 0.1f); - ensure_equals("high point went before the last", idx_hi, 1); - ensure_equals("low point went before the high one", idx_lo, 1); - ensure_equals("four points", mCurve.getPointCount(), 4); + UNSCOPED_INFO("high point went before the last"); + CHECK(idx_hi == 1); + UNSCOPED_INFO("low point went before the high one"); + CHECK(idx_lo == 1); + REQUIRE(mCurve.getPointCount() == 4); const auto& p = mCurve.getPoints(); for (size_t i = 1; i < p.size(); ++i) { - ensure("x is ascending", p[i].mX > p[i - 1].mX); + CAPTURE(i); + REQUIRE(p[i].mX > p[i - 1].mX); } - ensure_approximately_equals("second point is the low one", p[1].mX, 0.2f, 5); - ensure_approximately_equals("third point is the high one", p[2].mX, 0.8f, 5); + UNSCOPED_INFO("second point is the low one"); + CHECK_THAT(p[1].mX, ll_test::WithinBits(0.2f, 5)); + UNSCOPED_INFO("third point is the high one"); + CHECK_THAT(p[2].mX, ll_test::WithinBits(0.8f, 5)); } // Coincident points are pushed apart rather than sharing an x, so no // segment can have zero width. - template<> template<> - void curve_object::test<3>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: coincident points are pushed apart by at least the minimum gap", "[ALCurveModel]") { mCurve.setPoints(pts({ { 0.f, 0.f }, { 0.5f, 0.3f }, { 0.5f, 0.7f }, { 1.f, 1.f } })); const auto& p = mCurve.getPoints(); - ensure_equals("kept all four", (S32)p.size(), 4); + CHECK((S32)p.size() == 4); for (size_t i = 1; i < p.size(); ++i) { - ensure("gap is at least MIN_POINT_GAP", - p[i].mX - p[i - 1].mX >= ALCurveModel::MIN_POINT_GAP - 1e-6f); + CAPTURE(i); + REQUIRE(p[i].mX - p[i - 1].mX >= ALCurveModel::MIN_POINT_GAP - 1e-6f); } - ensure("evaluation is finite", std::isfinite(mCurve.evaluate(0.5f))); + CHECK(std::isfinite(mCurve.evaluate(0.5f))); } // A drag cannot push a point past its neighbours, and locked endpoints // keep their x while still accepting a new height. - template<> template<> - void curve_object::test<4>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: a drag stops at the neighbours and locked ends keep their x", "[ALCurveModel]") { mCurve.setPoints(pts({ { 0.f, 0.f }, { 0.3f, 0.3f }, { 0.6f, 0.6f }, { 1.f, 1.f } })); mCurve.movePoint(1, 0.95f, 0.5f); const auto& p = mCurve.getPoints(); - ensure("clamped below its right neighbour", p[1].mX < p[2].mX); - ensure_approximately_equals("clamped to exactly the gap", - p[1].mX, 0.6f - ALCurveModel::MIN_POINT_GAP, 5); - ensure("still ordered", p[0].mX < p[1].mX && p[2].mX < p[3].mX); + UNSCOPED_INFO("clamped below its right neighbour"); + CHECK(p[1].mX < p[2].mX); + UNSCOPED_INFO("clamped to exactly the gap"); + CHECK_THAT(p[1].mX, ll_test::WithinBits(0.6f - ALCurveModel::MIN_POINT_GAP, 5)); + { + INFO("still ordered"); + CHECK(p[0].mX < p[1].mX); + CHECK(p[2].mX < p[3].mX); + } mCurve.movePoint(0, 0.4f, 0.25f); - ensure_approximately_equals("first point x stays pinned", mCurve.getPoints()[0].mX, 0.f, 6); - ensure_approximately_equals("first point y moved", mCurve.getPoints()[0].mY, 0.25f, 5); + UNSCOPED_INFO("first point x stays pinned"); + CHECK_THAT(mCurve.getPoints()[0].mX, ll_test::WithinBits(0.f, 6)); + UNSCOPED_INFO("first point y moved"); + CHECK_THAT(mCurve.getPoints()[0].mY, ll_test::WithinBits(0.25f, 5)); mCurve.movePoint(3, 0.4f, 0.8f); - ensure_approximately_equals("last point x stays pinned", mCurve.getPoints()[3].mX, 1.f, 6); + UNSCOPED_INFO("last point x stays pinned"); + CHECK_THAT(mCurve.getPoints()[3].mX, ll_test::WithinBits(1.f, 6)); } // Removal refuses to break the curve: never below two points, and never an // endpoint while the endpoints are locked. - template<> template<> - void curve_object::test<5>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: removal never leaves fewer than two points or drops a locked end", "[ALCurveModel]") { mCurve.setPoints(pts({ { 0.f, 0.f }, { 0.5f, 0.4f }, { 1.f, 1.f } })); - ensure("cannot remove the first", !mCurve.removePoint(0)); - ensure("cannot remove the last", !mCurve.removePoint(2)); - ensure("out of range refused", !mCurve.removePoint(7)); - ensure("interior removed", mCurve.removePoint(1)); - ensure_equals("two left", mCurve.getPointCount(), 2); - ensure("cannot go below two", !mCurve.removePoint(0)); + UNSCOPED_INFO("cannot remove the first"); + CHECK_FALSE(mCurve.removePoint(0)); + UNSCOPED_INFO("cannot remove the last"); + CHECK_FALSE(mCurve.removePoint(2)); + UNSCOPED_INFO("out of range refused"); + CHECK_FALSE(mCurve.removePoint(7)); + UNSCOPED_INFO("interior removed"); + CHECK(mCurve.removePoint(1)); + CHECK(mCurve.getPointCount() == 2); + UNSCOPED_INFO("cannot go below two"); + CHECK_FALSE(mCurve.removePoint(0)); } // --- spline: shape ------------------------------------------------------- - // The curve passes through every control point. - template<> template<> - void curve_object::test<6>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: the curve passes through every control point", "[ALCurveModel]") { mCurve.setPoints(pts({ { 0.f, 0.05f }, { 0.25f, 0.5f }, { 0.7f, 0.6f }, { 1.f, 0.95f } })); for (const auto& p : mCurve.getPoints()) { - ensure_approximately_equals("interpolates its points", - mCurve.evaluate(p.mX), p.mY, 4); + CAPTURE(p.mX); + REQUIRE_THAT(mCurve.evaluate(p.mX), ll_test::WithinBits(p.mY, 4)); } } // Monotone data yields a monotone curve. A natural or Catmull-Rom spline // fails this on exactly this shape -- a long flat run into a sharp rise // makes it dip below the flat before climbing. - template<> template<> - void curve_object::test<7>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: monotone data yields a monotone curve", "[ALCurveModel]") { mCurve.setPoints(pts({ { 0.f, 0.f }, { 0.4f, 0.02f }, { 0.6f, 0.05f }, { 0.7f, 0.9f }, { 1.f, 1.f } })); @@ -220,39 +227,39 @@ namespace tut mCurve.sample(s, 257); for (size_t i = 1; i < s.size(); ++i) { - ensure("never decreases", s[i] >= s[i - 1] - 1e-5f); + CAPTURE(i); + REQUIRE(s[i] >= s[i - 1] - 1e-5f); } } - // A flat run stays exactly flat -- no ringing between equal-valued points. - template<> template<> - void curve_object::test<8>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: a flat run stays exactly flat with no ringing between equal points", "[ALCurveModel]") { mCurve.setPoints(pts({ { 0.f, 0.f }, { 0.3f, 0.5f }, { 0.7f, 0.5f }, { 1.f, 1.f } })); for (S32 i = 0; i <= 8; ++i) { const F32 x = 0.3f + i * 0.05f; - ensure_approximately_equals("flat between equal points", mCurve.evaluate(x), 0.5f, 4); + CAPTURE(x); + REQUIRE_THAT(mCurve.evaluate(x), ll_test::WithinBits(0.5f, 4)); } } // Outside the point range the curve holds its end values rather than // extrapolating off the graph. - template<> template<> - void curve_object::test<9>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: outside its points the curve holds its end values", "[ALCurveModel]") { mCurve.setEndpointsLocked(false); mCurve.setPoints(pts({ { 0.2f, 0.3f }, { 0.8f, 0.7f } })); - ensure_approximately_equals("holds the left value", mCurve.evaluate(0.f), 0.3f, 5); - ensure_approximately_equals("holds the right value", mCurve.evaluate(1.f), 0.7f, 5); + UNSCOPED_INFO("holds the left value"); + CHECK_THAT(mCurve.evaluate(0.f), ll_test::WithinBits(0.3f, 5)); + UNSCOPED_INFO("holds the right value"); + CHECK_THAT(mCurve.evaluate(1.f), ll_test::WithinBits(0.7f, 5)); } // Output never leaves 0..1, so a curve can never ask the caller to write an // out-of-gamut value. - template<> template<> - void curve_object::test<10>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: output never leaves 0..1 even for a wild curve", "[ALCurveModel]") { mCurve.setPoints(pts({ { 0.f, 0.f }, { 0.05f, 0.99f }, { 0.1f, 0.01f }, { 1.f, 1.f } })); @@ -260,70 +267,77 @@ namespace tut mCurve.sample(s, 129); for (F32 y : s) { - ensure("in range", y >= 0.f && y <= 1.f); - ensure("finite", std::isfinite(y)); + REQUIRE(y >= 0.f); + REQUIRE(y <= 1.f); + REQUIRE(std::isfinite(y)); } } // --- sampling ------------------------------------------------------------ - // sample() spans 0..1 inclusive and honours its count. - template<> template<> - void curve_object::test<11>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: sample() spans 0..1 inclusive and honours its count", "[ALCurveModel]") { mCurve.setPoints(pts({ { 0.f, 0.1f }, { 0.5f, 0.4f }, { 1.f, 0.9f } })); std::vector s; mCurve.sample(s, 65); - ensure_equals("count honoured", (S32)s.size(), 65); - ensure_approximately_equals("starts at evaluate(0)", s.front(), mCurve.evaluate(0.f), 6); - ensure_approximately_equals("ends at evaluate(1)", s.back(), mCurve.evaluate(1.f), 6); - ensure_approximately_equals("the last sample is the value at exactly 1", s.back(), 0.9f, 6); + REQUIRE((S32)s.size() == 65); + CHECK_THAT(s.front(), ll_test::WithinBits(mCurve.evaluate(0.f), 6)); + CHECK_THAT(s.back(), ll_test::WithinBits(mCurve.evaluate(1.f), 6)); + UNSCOPED_INFO("the last sample is the value at exactly 1"); + CHECK_THAT(s.back(), ll_test::WithinBits(0.9f, 6)); mCurve.sample(s, 1); - ensure("a single sample is not a curve", s.empty()); + UNSCOPED_INFO("a single sample is not a curve"); + CHECK(s.empty()); mCurve.sample(s, 0); - ensure("zero samples", s.empty()); + CHECK(s.empty()); } // Unlocking the endpoints lets the first and last points move horizontally; // re-locking snaps them back to the full domain. - template<> template<> - void curve_object::test<12>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: unlocked ends move horizontally and relocking snaps them back", "[ALCurveModel]") { mCurve.setEndpointsLocked(false); mCurve.setPoints(pts({ { 0.f, 0.f }, { 0.5f, 0.5f }, { 1.f, 1.f } })); mCurve.movePoint(0, 0.2f, 0.1f); - ensure_approximately_equals("unlocked end moved", mCurve.getPoints()[0].mX, 0.2f, 5); + UNSCOPED_INFO("unlocked end moved"); + CHECK_THAT(mCurve.getPoints()[0].mX, ll_test::WithinBits(0.2f, 5)); mCurve.setEndpointsLocked(true); - ensure_approximately_equals("relock snaps to 0", mCurve.getPoints()[0].mX, 0.f, 6); - ensure_approximately_equals("relock snaps to 1", mCurve.getPoints().back().mX, 1.f, 6); + UNSCOPED_INFO("relock snaps to 0"); + CHECK_THAT(mCurve.getPoints()[0].mX, ll_test::WithinBits(0.f, 6)); + UNSCOPED_INFO("relock snaps to 1"); + CHECK_THAT(mCurve.getPoints().back().mX, ll_test::WithinBits(1.f, 6)); } // setPoints refuses to leave the model unusable. - template<> template<> - void curve_object::test<13>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: setPoints resets a single point to the identity ramp", "[ALCurveModel]") { mCurve.setPoints(pts({ { 0.3f, 0.3f } })); - ensure_equals("degenerate input resets to a ramp", mCurve.getPointCount(), 2); - ensure_approximately_equals("and it is the identity", mCurve.evaluate(0.5f), 0.5f, 5); + UNSCOPED_INFO("degenerate input resets to a ramp"); + CHECK(mCurve.getPointCount() == 2); + UNSCOPED_INFO("and it is the identity"); + CHECK_THAT(mCurve.evaluate(0.5f), ll_test::WithinBits(0.5f, 5)); } // The point cap: addPoint refuses at MAX_POINTS, and setPoints truncates // while keeping both ends, so a locked curve still spans the domain. - template<> template<> - void curve_object::test<14>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: addPoint stops at MAX_POINTS and setPoints keeps both ends", "[ALCurveModel]") { for (S32 i = 1; i <= ALCurveModel::MAX_POINTS - 2; ++i) { const F32 x = (F32)i / (F32)(ALCurveModel::MAX_POINTS - 1); - ensure("added while under the cap", mCurve.addPoint(x, x) >= 0); + CAPTURE(i); + REQUIRE(mCurve.addPoint(x, x) >= 0); } - ensure_equals("at the cap", mCurve.getPointCount(), ALCurveModel::MAX_POINTS); - ensure_equals("refused past the cap", mCurve.addPoint(0.123f, 0.5f), -1); - ensure_equals("count unchanged", mCurve.getPointCount(), ALCurveModel::MAX_POINTS); + UNSCOPED_INFO("at the cap"); + CHECK(mCurve.getPointCount() == ALCurveModel::MAX_POINTS); + UNSCOPED_INFO("refused past the cap"); + CHECK(mCurve.addPoint(0.123f, 0.5f) == -1); + UNSCOPED_INFO("count unchanged"); + CHECK(mCurve.getPointCount() == ALCurveModel::MAX_POINTS); // A stored curve may carry more than the editor adds, and keeps every // point: truncating a file would render a shape the file does not @@ -335,9 +349,12 @@ namespace tut many.push_back(ALCurveModel::Point{ x, x * 0.5f }); } mCurve.setPoints(many); - ensure_equals("twenty stored points survive", mCurve.getPointCount(), 20); - ensure_approximately_equals("interior point kept", mCurve.getPoints()[15].mX, 15.f / 19.f, 5); - ensure_equals("but no further adds", mCurve.addPoint(0.123f, 0.5f), -1); + UNSCOPED_INFO("twenty stored points survive"); + REQUIRE(mCurve.getPointCount() == 20); + UNSCOPED_INFO("interior point kept"); + CHECK_THAT(mCurve.getPoints()[15].mX, ll_test::WithinBits(15.f / 19.f, 5)); + UNSCOPED_INFO("but no further adds"); + CHECK(mCurve.addPoint(0.123f, 0.5f) == -1); std::vector absurd; for (S32 i = 0; i < 100; ++i) @@ -346,10 +363,14 @@ namespace tut absurd.push_back(ALCurveModel::Point{ x, x * 0.5f }); } mCurve.setPoints(absurd); - ensure_equals("setPoints capped at the stored limit", mCurve.getPointCount(), ALCurveModel::MAX_STORED_POINTS); - ensure_approximately_equals("first point kept at 0", mCurve.getPoints().front().mX, 0.f, 6); - ensure_approximately_equals("last point kept at 1", mCurve.getPoints().back().mX, 1.f, 6); - ensure_approximately_equals("and it is the original last point", mCurve.getPoints().back().mY, 0.5f, 5); + UNSCOPED_INFO("setPoints capped at the stored limit"); + CHECK(mCurve.getPointCount() == ALCurveModel::MAX_STORED_POINTS); + UNSCOPED_INFO("first point kept at 0"); + CHECK_THAT(mCurve.getPoints().front().mX, ll_test::WithinBits(0.f, 6)); + UNSCOPED_INFO("last point kept at 1"); + CHECK_THAT(mCurve.getPoints().back().mX, ll_test::WithinBits(1.f, 6)); + UNSCOPED_INFO("and it is the original last point"); + CHECK_THAT(mCurve.getPoints().back().mY, ll_test::WithinBits(0.5f, 5)); } // --- split-tone bands ---------------------------------------------------- @@ -358,8 +379,7 @@ namespace tut // computed before the widths existed, across the whole range of split // points the Balance slider can ask for. This is the promise that every // Look saved before then renders unchanged. - template<> template<> - void curve_object::test<15>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: default widths give the bands the shader computed before widths existed", "[ALCurveModel]") { for (F32 mid : { 0.1f, 0.3f, 0.5f, 0.7f, 0.9f }) { @@ -369,13 +389,14 @@ namespace tut F32 lo, md, hi; shaderSplitWeights(l, mid, lo, md, hi); const auto w = ALCurveModel::splitToneWeights(l, mid); - ensure_approximately_equals("shadow matches the old shader", w.mShadow, lo, 5); - ensure_approximately_equals("midtone matches the old shader", w.mMidtone, md, 5); - ensure_approximately_equals("highlight matches the old shader", w.mHighlight, hi, 5); + CAPTURE(mid, l); + REQUIRE_THAT(w.mShadow, ll_test::WithinBits(lo, 5)); + REQUIRE_THAT(w.mMidtone, ll_test::WithinBits(md, 5)); + REQUIRE_THAT(w.mHighlight, ll_test::WithinBits(hi, 5)); } } - ensure_approximately_equals("the default width is the old constant", - ALCurveModel::SPLIT_TONE_DEFAULT_WIDTH, 0.35f, 6); + UNSCOPED_INFO("the default width is the old constant"); + CHECK_THAT(ALCurveModel::SPLIT_TONE_DEFAULT_WIDTH, ll_test::WithinBits(0.35f, 6)); } // The three weights partition the luma range: they sum to one everywhere. @@ -383,32 +404,39 @@ namespace tut // untinted, only ever shared between neighbouring bands -- and it holds // because the two ramps meet at the split without overlapping, so the // midtone remainder is never clamped away. - template<> template<> - void curve_object::test<16>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: the three split-tone weights sum to one everywhere", "[ALCurveModel]") { for (F32 mid : { 0.1f, 0.35f, 0.5f, 0.65f, 0.9f }) { for (S32 i = 0; i <= 64; ++i) { const auto w = ALCurveModel::splitToneWeights((F32)i / 64.f, mid); - ensure_approximately_equals("weights sum to one", - w.mShadow + w.mMidtone + w.mHighlight, 1.f, 5); - ensure("shadow in range", w.mShadow >= 0.f && w.mShadow <= 1.f); - ensure("midtone in range", w.mMidtone >= 0.f && w.mMidtone <= 1.f); - ensure("highlight in range", w.mHighlight >= 0.f && w.mHighlight <= 1.f); + CAPTURE(mid, i); + REQUIRE_THAT(w.mShadow + w.mMidtone + w.mHighlight, ll_test::WithinBits(1.f, 5)); + REQUIRE(w.mShadow >= 0.f); + REQUIRE(w.mShadow <= 1.f); + REQUIRE(w.mMidtone >= 0.f); + REQUIRE(w.mMidtone <= 1.f); + REQUIRE(w.mHighlight >= 0.f); + REQUIRE(w.mHighlight <= 1.f); } } const F32 mid = 0.55f; const auto at_mid = ALCurveModel::splitToneWeights(mid, mid); - ensure_approximately_equals("midtone peaks at the split", at_mid.mMidtone, 1.f, 5); - ensure_approximately_equals("shadow is spent there", at_mid.mShadow, 0.f, 5); - ensure_approximately_equals("highlight has not started", at_mid.mHighlight, 0.f, 5); + UNSCOPED_INFO("midtone peaks at the split"); + CHECK_THAT(at_mid.mMidtone, ll_test::WithinBits(1.f, 5)); + UNSCOPED_INFO("shadow is spent there"); + CHECK_THAT(at_mid.mShadow, ll_test::WithinBits(0.f, 5)); + UNSCOPED_INFO("highlight has not started"); + CHECK_THAT(at_mid.mHighlight, ll_test::WithinBits(0.f, 5)); const auto black = ALCurveModel::splitToneWeights(0.f, mid); const auto white = ALCurveModel::splitToneWeights(1.f, mid); - ensure_approximately_equals("black is all shadow", black.mShadow, 1.f, 5); - ensure_approximately_equals("white is all highlight", white.mHighlight, 1.f, 5); + UNSCOPED_INFO("black is all shadow"); + CHECK_THAT(black.mShadow, ll_test::WithinBits(1.f, 5)); + UNSCOPED_INFO("white is all highlight"); + CHECK_THAT(white.mHighlight, ll_test::WithinBits(1.f, 5)); // Monotone, so the bands cannot cross back over themselves. If the // shader's ramp edges were ever swapped, this is what notices. @@ -416,8 +444,9 @@ namespace tut for (S32 i = 0; i <= 64; ++i) { const auto w = ALCurveModel::splitToneWeights((F32)i / 64.f, mid); - ensure("highlight never falls", w.mHighlight >= prev_hi - 1e-5f); - ensure("shadow never rises", w.mShadow <= prev_lo + 1e-5f); + CAPTURE(i); + REQUIRE(w.mHighlight >= prev_hi - 1e-5f); + REQUIRE(w.mShadow <= prev_lo + 1e-5f); prev_hi = w.mHighlight; prev_lo = w.mShadow; } @@ -426,33 +455,32 @@ namespace tut // Balance and split point invert each other across the whole slider range, // which is what lets the graph's handle be read back into the setting // without the value creeping a little on every drag. - template<> template<> - void curve_object::test<17>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: balance and split point invert each other across the slider range", "[ALCurveModel]") { for (S32 i = -10; i <= 10; ++i) { const F32 balance = (F32)i / 10.f; const F32 mid = ALCurveModel::splitToneMid(balance); - ensure("split point stays in range", mid >= 0.1f - 1e-5f && mid <= 0.9f + 1e-5f); - ensure_approximately_equals("balance round-trips", - ALCurveModel::splitToneBalance(mid), balance, 5); + CAPTURE(balance); + REQUIRE(mid >= 0.1f - 1e-5f); + REQUIRE(mid <= 0.9f + 1e-5f); + REQUIRE_THAT(ALCurveModel::splitToneBalance(mid), ll_test::WithinBits(balance, 5)); } - ensure_approximately_equals("neutral balance splits at mid grey", - ALCurveModel::splitToneMid(0.f), 0.5f, 6); + UNSCOPED_INFO("neutral balance splits at mid grey"); + CHECK_THAT(ALCurveModel::splitToneMid(0.f), ll_test::WithinBits(0.5f, 6)); // Clamped, not wrapped: pipeline.cpp clamps the balance before it // uploads the ramps, so the graph must agree rather than plot a split // the renderer will never use. - ensure_approximately_equals("out-of-range balance clamps", - ALCurveModel::splitToneMid(3.f), 0.9f, 6); - ensure_approximately_equals("and so does the inverse", - ALCurveModel::splitToneBalance(2.f), 1.f, 6); + UNSCOPED_INFO("out-of-range balance clamps"); + CHECK_THAT(ALCurveModel::splitToneMid(3.f), ll_test::WithinBits(0.9f, 6)); + UNSCOPED_INFO("and so does the inverse"); + CHECK_THAT(ALCurveModel::splitToneBalance(2.f), ll_test::WithinBits(1.f, 6)); } // With independent widths the model still agrees with the shader, which // now takes each ramp as the {scale, bias} pair the CPU derives. - template<> template<> - void curve_object::test<18>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: independent widths agree with the shader's scale and bias ramps", "[ALCurveModel]") { const std::pair widths[] = { { 0.05f, 0.5f }, { 0.35f, 0.35f }, { 0.5f, 0.02f }, { 0.2f, 0.1f } }; for (F32 mid : { 0.1f, 0.5f, 0.9f }) @@ -465,9 +493,10 @@ namespace tut F32 lo, md, hi; shaderSplitWeightsRamped(l, mid, wsh.first, wsh.second, lo, md, hi); const auto w = ALCurveModel::splitToneWeights(l, mid, wsh.first, wsh.second); - ensure_approximately_equals("shadow matches the shader", w.mShadow, lo, 5); - ensure_approximately_equals("midtone matches the shader", w.mMidtone, md, 5); - ensure_approximately_equals("highlight matches the shader", w.mHighlight, hi, 5); + CAPTURE(mid, wsh.first, wsh.second, l); + REQUIRE_THAT(w.mShadow, ll_test::WithinBits(lo, 5)); + REQUIRE_THAT(w.mMidtone, ll_test::WithinBits(md, 5)); + REQUIRE_THAT(w.mHighlight, ll_test::WithinBits(hi, 5)); } } } @@ -475,31 +504,35 @@ namespace tut // Partition of unity survives asymmetric widths: the ramps still meet at // the split and only at the split. - template<> template<> - void curve_object::test<19>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: partition of unity survives asymmetric widths", "[ALCurveModel]") { const std::pair widths[] = { { 0.05f, 0.5f }, { 0.5f, 0.02f }, { 0.2f, 0.1f } }; for (F32 mid : { 0.1f, 0.5f, 0.9f }) { for (const auto& wsh : widths) { + CAPTURE(mid, wsh.first, wsh.second); F32 prev_hi = -1.f, prev_lo = 2.f; for (S32 i = 0; i <= 64; ++i) { const F32 l = (F32)i / 64.f; const auto w = ALCurveModel::splitToneWeights(l, mid, wsh.first, wsh.second); - ensure_approximately_equals("weights sum to one", - w.mShadow + w.mMidtone + w.mHighlight, 1.f, 5); - ensure("shadow in range", w.mShadow >= 0.f && w.mShadow <= 1.f); - ensure("midtone in range", w.mMidtone >= 0.f && w.mMidtone <= 1.f); - ensure("highlight in range", w.mHighlight >= 0.f && w.mHighlight <= 1.f); - ensure("highlight never falls", w.mHighlight >= prev_hi - 1e-5f); - ensure("shadow never rises", w.mShadow <= prev_lo + 1e-5f); + CAPTURE(l); + REQUIRE_THAT(w.mShadow + w.mMidtone + w.mHighlight, ll_test::WithinBits(1.f, 5)); + REQUIRE(w.mShadow >= 0.f); + REQUIRE(w.mShadow <= 1.f); + REQUIRE(w.mMidtone >= 0.f); + REQUIRE(w.mMidtone <= 1.f); + REQUIRE(w.mHighlight >= 0.f); + REQUIRE(w.mHighlight <= 1.f); + REQUIRE(w.mHighlight >= prev_hi - 1e-5f); + REQUIRE(w.mShadow <= prev_lo + 1e-5f); prev_hi = w.mHighlight; prev_lo = w.mShadow; } const auto at_mid = ALCurveModel::splitToneWeights(mid, mid, wsh.first, wsh.second); - ensure_approximately_equals("midtone peaks at the split", at_mid.mMidtone, 1.f, 5); + UNSCOPED_INFO("midtone peaks at the split"); + REQUIRE_THAT(at_mid.mMidtone, ll_test::WithinBits(1.f, 5)); // The ends are fully tinted only when the ramp fits on the // plot. A wide ramp under a low split runs off below zero and // black is then only partly shadow -- as it was with the old @@ -507,13 +540,13 @@ namespace tut // shows. if (mid - wsh.first >= 0.f) { - ensure_approximately_equals("black is all shadow", - ALCurveModel::splitToneWeights(0.f, mid, wsh.first, wsh.second).mShadow, 1.f, 5); + UNSCOPED_INFO("black is all shadow"); + REQUIRE_THAT(ALCurveModel::splitToneWeights(0.f, mid, wsh.first, wsh.second).mShadow, ll_test::WithinBits(1.f, 5)); } if (mid + wsh.second <= 1.f) { - ensure_approximately_equals("white is all highlight", - ALCurveModel::splitToneWeights(1.f, mid, wsh.first, wsh.second).mHighlight, 1.f, 5); + UNSCOPED_INFO("white is all highlight"); + REQUIRE_THAT(ALCurveModel::splitToneWeights(1.f, mid, wsh.first, wsh.second).mHighlight, ll_test::WithinBits(1.f, 5)); } } } @@ -522,28 +555,29 @@ namespace tut // A zero width is a step at the split, finite on both sides, and still a // partition -- the floor is applied before the shadow ramp's left edge is // formed, so the ramp ends at the split rather than short of it. - template<> template<> - void curve_object::test<20>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: a zero width is a finite step at the split and still a partition", "[ALCurveModel]") { const auto ramp = ALCurveModel::splitToneRamp(0.5f, 0.f); - ensure("scale finite", std::isfinite(ramp.mScale)); - ensure("bias finite", std::isfinite(ramp.mBias)); + CHECK(std::isfinite(ramp.mScale)); + CHECK(std::isfinite(ramp.mBias)); const auto below = ALCurveModel::splitToneWeights(0.49f, 0.5f, 0.f, 0.f); const auto above = ALCurveModel::splitToneWeights(0.51f, 0.5f, 0.f, 0.f); - ensure_approximately_equals("all shadow just below the split", below.mShadow, 1.f, 5); - ensure_approximately_equals("all highlight just above the split", above.mHighlight, 1.f, 5); + UNSCOPED_INFO("all shadow just below the split"); + CHECK_THAT(below.mShadow, ll_test::WithinBits(1.f, 5)); + UNSCOPED_INFO("all highlight just above the split"); + CHECK_THAT(above.mHighlight, ll_test::WithinBits(1.f, 5)); for (S32 i = 0; i <= 64; ++i) { const auto w = ALCurveModel::splitToneWeights((F32)i / 64.f, 0.5f, 0.f, 0.f); - ensure_approximately_equals("still sums to one", w.mShadow + w.mMidtone + w.mHighlight, 1.f, 5); + CAPTURE(i); + REQUIRE_THAT(w.mShadow + w.mMidtone + w.mHighlight, ll_test::WithinBits(1.f, 5)); } } // The scale/bias pair is the two-edge form folded: l*scale + bias is // (l - edge0) / width wherever the width is above the floor. - template<> template<> - void curve_object::test<21>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: the scale/bias pair is the two-edge ramp folded", "[ALCurveModel]") { const F32 edges[] = { 0.f, 0.15f, 0.5f, 0.9f }; const F32 widths[] = { 0.02f, 0.1f, 0.35f, 0.5f }; @@ -555,16 +589,14 @@ namespace tut for (S32 i = 0; i <= 20; ++i) { const F32 l = (F32)i / 20.f; - ensure_approximately_equals("folded form agrees", - l * ramp.mScale + ramp.mBias, (l - edge0) / width, 4); + CAPTURE(edge0, width, l); + REQUIRE_THAT(l * ramp.mScale + ramp.mBias, ll_test::WithinBits((l - edge0) / width, 4)); } } } } - // The two-argument overload is the four-argument call at the default. - template<> template<> - void curve_object::test<22>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: the two-argument overload is the four-argument call at the default", "[ALCurveModel]") { for (F32 mid : { 0.1f, 0.5f, 0.9f }) { @@ -573,93 +605,104 @@ namespace tut const F32 l = (F32)i / 32.f; const auto a = ALCurveModel::splitToneWeights(l, mid); const auto b = ALCurveModel::splitToneWeights(l, mid, 0.35f, 0.35f); - ensure_equals("shadow bit for bit", a.mShadow, b.mShadow); - ensure_equals("midtone bit for bit", a.mMidtone, b.mMidtone); - ensure_equals("highlight bit for bit", a.mHighlight, b.mHighlight); + CAPTURE(mid, l); + REQUIRE(a.mShadow == b.mShadow); + REQUIRE(a.mMidtone == b.mMidtone); + REQUIRE(a.mHighlight == b.mHighlight); } } } // --- ALToneCurveSet ------------------------------------------------------ - // A fresh set is identity in every channel, and bakes to the identity ramp. - template<> template<> - void curve_object::test<23>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: a fresh set is identity in every channel and bakes to the identity ramp", "[ALCurveModel]") { ALToneCurveSet set; - ensure("identity by default", set.isIdentity()); + CHECK(set.isIdentity()); for (S32 c = 0; c < ALToneCurveSet::CH_COUNT; ++c) { for (S32 i = 0; i <= 20; ++i) { const F32 x = (F32)i / 20.f; - ensure_approximately_equals("evaluates to x", set.evaluate((ALToneCurveSet::EChannel)c, x), x, 6); + CAPTURE(c, x); + REQUIRE_THAT(set.evaluate((ALToneCurveSet::EChannel)c, x), ll_test::WithinBits(x, 6)); } } std::vector texels; set.bake(texels, 512); - ensure_equals("four U16 per texel", (S32)texels.size(), 512 * 4); + UNSCOPED_INFO("four U16 per texel"); + REQUIRE((S32)texels.size() == 512 * 4); for (S32 i = 0; i < 512; ++i) { + CAPTURE(i); const S32 expected = (S32)std::floor(65535.0 * i / 511.0 + 0.5); for (S32 c = 0; c < 3; ++c) { // Within one code: the spline runs in F32, and a few of the // ratios sit close enough to a rounding boundary for the last // bit to decide which side. - ensure("identity ramp texel", std::abs((S32)texels[i * 4 + c] - expected) <= 1); + CAPTURE(c); + REQUIRE(std::abs((S32)texels[i * 4 + c] - expected) <= 1); } - ensure_equals("alpha opaque", (S32)texels[i * 4 + 3], (S32)ALToneCurveSet::ALPHA_OPAQUE); + REQUIRE((S32)texels[i * 4 + 3] == (S32)ALToneCurveSet::ALPHA_OPAQUE); } } // Points survive an LLSD round trip, in the array-of-pairs shape the // settings and Looks carry. - template<> template<> - void curve_object::test<24>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: points survive an LLSD round trip as an array of pairs", "[ALCurveModel]") { const auto in = pts({ { 0.f, 0.05f }, { 0.25f, 0.2f }, { 0.75f, 0.8f }, { 1.f, 0.95f } }); const LLSD sd = ALToneCurveSet::pointsToLLSD(in); - ensure("array", sd.isArray()); - ensure_equals("one entry per point", (S32)sd.size(), 4); + REQUIRE(sd.isArray()); + REQUIRE((S32)sd.size() == 4); for (S32 i = 0; i < 4; ++i) { - ensure("entry is an array", sd[i].isArray()); - ensure_equals("entry is a pair", (S32)sd[i].size(), 2); - ensure("x is a real", sd[i][0].isReal()); - ensure("y is a real", sd[i][1].isReal()); + CAPTURE(i); + REQUIRE(sd[i].isArray()); + REQUIRE((S32)sd[i].size() == 2); + REQUIRE(sd[i][0].isReal()); + REQUIRE(sd[i][1].isReal()); } std::vector out; - ensure("parsed", ALToneCurveSet::pointsFromLLSD(sd, out)); - ensure_equals("all points back", (S32)out.size(), 4); + CHECK(ALToneCurveSet::pointsFromLLSD(sd, out)); + REQUIRE((S32)out.size() == 4); for (S32 i = 0; i < 4; ++i) { - ensure_approximately_equals("x survives", out[i].mX, in[i].mX, 6); - ensure_approximately_equals("y survives", out[i].mY, in[i].mY, 6); + CAPTURE(i); + REQUIRE_THAT(out[i].mX, ll_test::WithinBits(in[i].mX, 6)); + REQUIRE_THAT(out[i].mY, ll_test::WithinBits(in[i].mY, 6)); } ALToneCurveSet set; - ensure("set from LLSD", set.setCurveFromLLSD(ALToneCurveSet::CH_GREEN, sd)); - ensure("no longer identity", !set.isIdentity()); + CHECK(set.setCurveFromLLSD(ALToneCurveSet::CH_GREEN, sd)); + UNSCOPED_INFO("no longer identity"); + CHECK_FALSE(set.isIdentity()); const LLSD back = set.getCurveAsLLSD(ALToneCurveSet::CH_GREEN); - ensure_equals("same count after the model", (S32)back.size(), 4); - ensure_approximately_equals("same value after the model", (F32)back[1][1].asReal(), 0.2f, 6); + UNSCOPED_INFO("same count after the model"); + REQUIRE((S32)back.size() == 4); + UNSCOPED_INFO("same value after the model"); + CHECK_THAT((F32)back[1][1].asReal(), ll_test::WithinBits(0.2f, 6)); } // Garbage in is identity out, and never a crash or a point at the origin: // a corrupt setting renders as "no curve". - template<> template<> - void curve_object::test<25>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: garbage LLSD is refused or skipped and leaves an identity curve", "[ALCurveModel]") { std::vector out; - ensure("undefined refused", !ALToneCurveSet::pointsFromLLSD(LLSD(), out)); - ensure_equals("identity on refusal", (S32)out.size(), 2); - ensure("string refused", !ALToneCurveSet::pointsFromLLSD(LLSD("[[0,0],[1,1]]"), out)); - ensure("map refused", !ALToneCurveSet::pointsFromLLSD(LLSD::emptyMap(), out)); - ensure("empty array refused", !ALToneCurveSet::pointsFromLLSD(LLSD::emptyArray(), out)); + UNSCOPED_INFO("undefined refused"); + CHECK_FALSE(ALToneCurveSet::pointsFromLLSD(LLSD(), out)); + UNSCOPED_INFO("identity on refusal"); + CHECK((S32)out.size() == 2); + UNSCOPED_INFO("string refused"); + CHECK_FALSE(ALToneCurveSet::pointsFromLLSD(LLSD("[[0,0],[1,1]]"), out)); + UNSCOPED_INFO("map refused"); + CHECK_FALSE(ALToneCurveSet::pointsFromLLSD(LLSD::emptyMap(), out)); + UNSCOPED_INFO("empty array refused"); + CHECK_FALSE(ALToneCurveSet::pointsFromLLSD(LLSD::emptyArray(), out)); // Malformed entries are skipped, not fatal; the survivors are used. LLSD mixed = LLSD::emptyArray(); @@ -676,25 +719,31 @@ namespace tut mixed.append(pair(LLSD::Real(0.0), LLSD::Real(0.0))); mixed.append(pair(LLSD::Integer(1), LLSD::Integer(1))); // integers are numbers too mixed.append(pair(LLSD::Real(0.5), LLSD::Real(0.3))); - ensure("survivors accepted", ALToneCurveSet::pointsFromLLSD(mixed, out)); - ensure_equals("exactly the three well-formed pairs", (S32)out.size(), 3); + UNSCOPED_INFO("survivors accepted"); + CHECK(ALToneCurveSet::pointsFromLLSD(mixed, out)); + UNSCOPED_INFO("exactly the three well-formed pairs"); + CHECK((S32)out.size() == 3); // One well-formed pair is not a curve. LLSD one = LLSD::emptyArray(); one.append(pair(LLSD::Real(0.5), LLSD::Real(0.5))); - ensure("a lone pair refused", !ALToneCurveSet::pointsFromLLSD(one, out)); - ensure_equals("identity again", (S32)out.size(), 2); + UNSCOPED_INFO("a lone pair refused"); + CHECK_FALSE(ALToneCurveSet::pointsFromLLSD(one, out)); + UNSCOPED_INFO("identity again"); + CHECK((S32)out.size() == 2); // Out of range is clamped by the model, not rejected. LLSD wild = LLSD::emptyArray(); wild.append(pair(LLSD::Real(-1.0), LLSD::Real(-1.0))); wild.append(pair(LLSD::Real(2.0), LLSD::Real(2.0))); ALToneCurveSet set; - ensure("parsed", set.setCurveFromLLSD(ALToneCurveSet::CH_RED, wild)); + CHECK(set.setCurveFromLLSD(ALToneCurveSet::CH_RED, wild)); for (const auto& p : set.curve(ALToneCurveSet::CH_RED).getPoints()) { - ensure("x clamped", p.mX >= 0.f && p.mX <= 1.f); - ensure("y clamped", p.mY >= 0.f && p.mY <= 1.f); + REQUIRE(p.mX >= 0.f); + REQUIRE(p.mX <= 1.f); + REQUIRE(p.mY >= 0.f); + REQUIRE(p.mY <= 1.f); } // More points than the editor adds are kept as long as a file could @@ -705,12 +754,13 @@ namespace tut const F64 x = i / 39.0; many.append(pair(LLSD::Real(x), LLSD::Real(x * x))); } - ensure("parsed", set.setCurveFromLLSD(ALToneCurveSet::CH_BLUE, many)); + CHECK(set.setCurveFromLLSD(ALToneCurveSet::CH_BLUE, many)); const auto& kept = set.curve(ALToneCurveSet::CH_BLUE).getPoints(); - ensure_equals("all forty kept", (S32)kept.size(), 40); - ensure_approximately_equals("first at 0", kept.front().mX, 0.f, 6); - ensure_approximately_equals("last at 1", kept.back().mX, 1.f, 6); - ensure_approximately_equals("shape honoured", set.curve(ALToneCurveSet::CH_BLUE).evaluate(0.5f), 0.25f, 4); + REQUIRE((S32)kept.size() == 40); + CHECK_THAT(kept.front().mX, ll_test::WithinBits(0.f, 6)); + CHECK_THAT(kept.back().mX, ll_test::WithinBits(1.f, 6)); + UNSCOPED_INFO("shape honoured"); + CHECK_THAT(set.curve(ALToneCurveSet::CH_BLUE).evaluate(0.5f), ll_test::WithinBits(0.25f, 4)); LLSD absurd = LLSD::emptyArray(); for (S32 i = 0; i <= ALCurveModel::MAX_STORED_POINTS; ++i) @@ -718,62 +768,64 @@ namespace tut const F64 x = (F64)i / ALCurveModel::MAX_STORED_POINTS; absurd.append(pair(LLSD::Real(x), LLSD::Real(x))); } - ensure("past the stored limit is refused", !set.setCurveFromLLSD(ALToneCurveSet::CH_BLUE, absurd)); - ensure_equals("and renders as no curve", set.curve(ALToneCurveSet::CH_BLUE).getPointCount(), 2); + UNSCOPED_INFO("past the stored limit is refused"); + CHECK_FALSE(set.setCurveFromLLSD(ALToneCurveSet::CH_BLUE, absurd)); + UNSCOPED_INFO("and renders as no curve"); + CHECK(set.curve(ALToneCurveSet::CH_BLUE).getPointCount() == 2); } - // Composite order: the channel curve first, then master on its output. - template<> template<> - void curve_object::test<26>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: a channel's curve applies first and master on its output", "[ALCurveModel]") { ALToneCurveSet set; set.curve(ALToneCurveSet::CH_RED).setPoints(pts({ { 0.f, 0.f }, { 0.5f, 0.25f }, { 1.f, 1.f } })); set.curve(ALToneCurveSet::CH_MASTER).setPoints(pts({ { 0.f, 0.f }, { 0.25f, 0.75f }, { 1.f, 1.f } })); - ensure_approximately_equals("red then master", set.evaluate(ALToneCurveSet::CH_RED, 0.5f), 0.75f, 4); - ensure_approximately_equals("is master(red(x))", - set.evaluate(ALToneCurveSet::CH_RED, 0.5f), - set.curve(ALToneCurveSet::CH_MASTER).evaluate(set.curve(ALToneCurveSet::CH_RED).evaluate(0.5f)), 6); - ensure_approximately_equals("an identity channel is just master", - set.evaluate(ALToneCurveSet::CH_GREEN, 0.5f), - set.curve(ALToneCurveSet::CH_MASTER).evaluate(0.5f), 6); - ensure_approximately_equals("master alone is master", - set.evaluate(ALToneCurveSet::CH_MASTER, 0.5f), - set.curve(ALToneCurveSet::CH_MASTER).evaluate(0.5f), 6); + UNSCOPED_INFO("red then master"); + CHECK_THAT(set.evaluate(ALToneCurveSet::CH_RED, 0.5f), ll_test::WithinBits(0.75f, 4)); + UNSCOPED_INFO("is master(red(x))"); + CHECK_THAT(set.evaluate(ALToneCurveSet::CH_RED, 0.5f), + ll_test::WithinBits(set.curve(ALToneCurveSet::CH_MASTER).evaluate(set.curve(ALToneCurveSet::CH_RED).evaluate(0.5f)), 6)); + UNSCOPED_INFO("an identity channel is just master"); + CHECK_THAT(set.evaluate(ALToneCurveSet::CH_GREEN, 0.5f), + ll_test::WithinBits(set.curve(ALToneCurveSet::CH_MASTER).evaluate(0.5f), 6)); + UNSCOPED_INFO("master alone is master"); + CHECK_THAT(set.evaluate(ALToneCurveSet::CH_MASTER, 0.5f), + ll_test::WithinBits(set.curve(ALToneCurveSet::CH_MASTER).evaluate(0.5f), 6)); // A master edit moves every channel. ALToneCurveSet master_only; master_only.curve(ALToneCurveSet::CH_MASTER).setPoints(pts({ { 0.f, 0.1f }, { 1.f, 0.9f } })); for (S32 c = ALToneCurveSet::CH_RED; c <= ALToneCurveSet::CH_BLUE; ++c) { - ensure_approximately_equals("every channel follows master", - master_only.evaluate((ALToneCurveSet::EChannel)c, 0.f), 0.1f, 5); + CAPTURE(c); + REQUIRE_THAT(master_only.evaluate((ALToneCurveSet::EChannel)c, 0.f), ll_test::WithinBits(0.1f, 5)); } } // Identity detection is exact on the points, with a tolerance far below // anything visible, and it is per set: one non-identity channel is enough. - template<> template<> - void curve_object::test<27>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: identity is detected within a tiny tolerance and per set", "[ALCurveModel]") { ALCurveModel close_curve; close_curve.setPoints(pts({ { 0.f, 0.f }, { 0.5f, 0.5f + 0.5f * ALToneCurveSet::IDENTITY_EPS }, { 1.f, 1.f } })); - ensure("within eps is identity", ALToneCurveSet::isIdentityCurve(close_curve)); + UNSCOPED_INFO("within eps is identity"); + CHECK(ALToneCurveSet::isIdentityCurve(close_curve)); ALCurveModel far_curve; far_curve.setPoints(pts({ { 0.f, 0.f }, { 0.5f, 0.5f + 10.f * ALToneCurveSet::IDENTITY_EPS }, { 1.f, 1.f } })); - ensure("beyond eps is not", !ALToneCurveSet::isIdentityCurve(far_curve)); + UNSCOPED_INFO("beyond eps is not"); + CHECK_FALSE(ALToneCurveSet::isIdentityCurve(far_curve)); ALToneCurveSet set; - ensure("all identity", set.isIdentity()); + CHECK(set.isIdentity()); set.curve(ALToneCurveSet::CH_BLUE).setPoints(pts({ { 0.f, 0.f }, { 0.5f, 0.6f }, { 1.f, 1.f } })); - ensure("one channel is enough", !set.isIdentity()); + UNSCOPED_INFO("one channel is enough"); + CHECK_FALSE(set.isIdentity()); } // The baked row: one RGBA16 texel per sample, the curve's ends at the // row's ends, and a monotone curve gives a monotone row. - template<> template<> - void curve_object::test<28>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: the baked row holds the curve's ends and is monotone and opaque", "[ALCurveModel]") { ALToneCurveSet set; set.curve(ALToneCurveSet::CH_MASTER).setPoints( @@ -783,28 +835,34 @@ namespace tut const S32 n = ALToneCurveSet::LUT_SIZE; std::vector texels; set.bake(texels, n); - ensure_equals("size", (S32)texels.size(), n * 4); + REQUIRE((S32)texels.size() == n * 4); for (S32 c = 0; c < 3; ++c) { const ALToneCurveSet::EChannel channel = (ALToneCurveSet::EChannel)(ALToneCurveSet::CH_RED + c); const F32 lo = set.evaluate(channel, 0.f) * 65535.f; const F32 hi = set.evaluate(channel, 1.f) * 65535.f; - ensure("first texel is the curve at 0", fabsf((F32)texels[c] - lo) <= 1.f); - ensure("last texel is the curve at 1", fabsf((F32)texels[(n - 1) * 4 + c] - hi) <= 1.f); + CAPTURE(c); + UNSCOPED_INFO("first texel is the curve at 0"); + REQUIRE(fabsf((F32)texels[c] - lo) <= 1.f); + UNSCOPED_INFO("last texel is the curve at 1"); + REQUIRE(fabsf((F32)texels[(n - 1) * 4 + c] - hi) <= 1.f); for (S32 i = 1; i < n; ++i) { - ensure("monotone row", texels[i * 4 + c] >= texels[(i - 1) * 4 + c]); + CAPTURE(i); + REQUIRE(texels[i * 4 + c] >= texels[(i - 1) * 4 + c]); } } for (S32 i = 0; i < n; ++i) { - ensure_equals("alpha opaque", (S32)texels[i * 4 + 3], (S32)ALToneCurveSet::ALPHA_OPAQUE); + CAPTURE(i); + REQUIRE((S32)texels[i * 4 + 3] == (S32)ALToneCurveSet::ALPHA_OPAQUE); } std::vector tiny; set.bake(tiny, 1); - ensure_equals("a size below 2 is raised to 2", (S32)tiny.size(), 2 * 4); + UNSCOPED_INFO("a size below 2 is raised to 2"); + CHECK((S32)tiny.size() == 2 * 4); } // What the shader will read back agrees with the analytic curve to well @@ -814,8 +872,7 @@ namespace tut // curves at 512 samples with 8-bit filter weights, and 0.50/1024 even with // the 4-bit weights old hardware is allowed; at 256 samples the 4-bit case // reaches 0.97/1024, which is why 512 is the size that ships. - template<> template<> - void curve_object::test<29>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: the shader's LUT fetch agrees with the curve to under 1/1024", "[ALCurveModel]") { ALToneCurveSet set; set.curve(ALToneCurveSet::CH_MASTER).setPoints( @@ -844,18 +901,19 @@ namespace tut { worst = std::max(worst, fabsf(shaderLutFetch(texels, n, c, x) - set.evaluate(channel, x))); } - ensure("endpoint 0 exact to the U16", - fabsf(shaderLutFetch(texels, n, c, 0.f) - set.evaluate(channel, 0.f)) <= 1.f / 65535.f + 1e-6f); - ensure("endpoint 1 exact to the U16", - fabsf(shaderLutFetch(texels, n, c, 1.f) - set.evaluate(channel, 1.f)) <= 1.f / 65535.f + 1e-6f); + CAPTURE(c); + UNSCOPED_INFO("endpoint 0 exact to the U16"); + REQUIRE(fabsf(shaderLutFetch(texels, n, c, 0.f) - set.evaluate(channel, 0.f)) <= 1.f / 65535.f + 1e-6f); + UNSCOPED_INFO("endpoint 1 exact to the U16"); + REQUIRE(fabsf(shaderLutFetch(texels, n, c, 1.f) - set.evaluate(channel, 1.f)) <= 1.f / 65535.f + 1e-6f); } - ensure("reconstruction error below 1/1024", worst < 1.f / 1024.f); + UNSCOPED_INFO("reconstruction error below 1/1024"); + CHECK(worst < 1.f / 1024.f); } // The half-texel mapping puts x = 0 on the centre of the first texel and // x = 1 on the centre of the last, for any row width. - template<> template<> - void curve_object::test<30>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: the half-texel mapping puts 0 and 1 on the end texels' centres", "[ALCurveModel]") { for (S32 n : { 2, 256, 512 }) { @@ -863,85 +921,99 @@ namespace tut ALToneCurveSet::lutScaleBias(n, scale, bias); const F32 u0 = 0.f * scale + bias; const F32 u1 = 1.f * scale + bias; - ensure_approximately_equals("x = 0 is texel 0's centre", u0 * (F32)n - 0.5f, 0.f, 6); - ensure_approximately_equals("x = 1 is the last texel's centre", u1 * (F32)n - 0.5f, (F32)(n - 1), 6); + CAPTURE(n); + REQUIRE_THAT(u0 * (F32)n - 0.5f, ll_test::WithinBits(0.f, 6)); + REQUIRE_THAT(u1 * (F32)n - 0.5f, ll_test::WithinBits((F32)(n - 1), 6)); } } - // The channel combo's values map onto the set, with anything unexpected - // landing on Master rather than on a channel. - template<> template<> - void curve_object::test<31>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: combo values map onto channels and anything else onto master", "[ALCurveModel]") { - ensure_equals("-1 is master", (S32)ALToneCurveSet::channelFromCombo(-1), (S32)ALToneCurveSet::CH_MASTER); - ensure_equals("0 is red", (S32)ALToneCurveSet::channelFromCombo(0), (S32)ALToneCurveSet::CH_RED); - ensure_equals("1 is green", (S32)ALToneCurveSet::channelFromCombo(1), (S32)ALToneCurveSet::CH_GREEN); - ensure_equals("2 is blue", (S32)ALToneCurveSet::channelFromCombo(2), (S32)ALToneCurveSet::CH_BLUE); - ensure_equals("anything else is master", (S32)ALToneCurveSet::channelFromCombo(7), (S32)ALToneCurveSet::CH_MASTER); + CHECK((S32)ALToneCurveSet::channelFromCombo(-1) == (S32)ALToneCurveSet::CH_MASTER); + CHECK((S32)ALToneCurveSet::channelFromCombo(0) == (S32)ALToneCurveSet::CH_RED); + CHECK((S32)ALToneCurveSet::channelFromCombo(1) == (S32)ALToneCurveSet::CH_GREEN); + CHECK((S32)ALToneCurveSet::channelFromCombo(2) == (S32)ALToneCurveSet::CH_BLUE); + UNSCOPED_INFO("anything else is master"); + CHECK((S32)ALToneCurveSet::channelFromCombo(7) == (S32)ALToneCurveSet::CH_MASTER); const LLSD ident = ALToneCurveSet::identityLLSD(); - ensure_equals("identity is two points", (S32)ident.size(), 2); - ensure_approximately_equals("from the origin", (F32)ident[0][0].asReal(), 0.f, 6); - ensure_approximately_equals("to the corner", (F32)ident[1][1].asReal(), 1.f, 6); + UNSCOPED_INFO("identity is two points"); + REQUIRE((S32)ident.size() == 2); + UNSCOPED_INFO("from the origin"); + CHECK_THAT((F32)ident[0][0].asReal(), ll_test::WithinBits(0.f, 6)); + UNSCOPED_INFO("to the corner"); + CHECK_THAT((F32)ident[1][1].asReal(), ll_test::WithinBits(1.f, 6)); } // An add at either edge lands one gap inside the pinned end, never on it, // so the curve still ends where the end says and the end stays the end. - template<> template<> - void curve_object::test<32>() - { - ensure("added at the right edge", mCurve.addPoint(1.f, 0.03f) >= 0); - ensure_equals("three points", mCurve.getPointCount(), 3); - ensure_approximately_equals("interior, one gap inside", mCurve.getPoints()[1].mX, 1.f - ALCurveModel::MIN_POINT_GAP, 5); - ensure_approximately_equals("the end is still the end", mCurve.getPoints().back().mX, 1.f, 6); - ensure_approximately_equals("and still at its height", mCurve.getPoints().back().mY, 1.f, 6); - ensure_approximately_equals("evaluate(1) is the end", mCurve.evaluate(1.f), 1.f, 6); - - ensure("added at the left edge", mCurve.addPoint(0.f, 0.9f) >= 0); - ensure_approximately_equals("the start is still the start", mCurve.getPoints().front().mX, 0.f, 6); - ensure_approximately_equals("and at its height", mCurve.getPoints().front().mY, 0.f, 6); - ensure_approximately_equals("interior, one gap inside", mCurve.getPoints()[1].mX, ALCurveModel::MIN_POINT_GAP, 5); - ensure_approximately_equals("evaluate(0) is the start", mCurve.evaluate(0.f), 0.f, 6); + TEST_CASE_METHOD(curve_data, "ALCurveModel: an add at either edge lands one gap inside the pinned end", "[ALCurveModel]") + { + UNSCOPED_INFO("added at the right edge"); + CHECK(mCurve.addPoint(1.f, 0.03f) >= 0); + REQUIRE(mCurve.getPointCount() == 3); + UNSCOPED_INFO("interior, one gap inside"); + CHECK_THAT(mCurve.getPoints()[1].mX, ll_test::WithinBits(1.f - ALCurveModel::MIN_POINT_GAP, 5)); + UNSCOPED_INFO("the end is still the end"); + CHECK_THAT(mCurve.getPoints().back().mX, ll_test::WithinBits(1.f, 6)); + UNSCOPED_INFO("and still at its height"); + CHECK_THAT(mCurve.getPoints().back().mY, ll_test::WithinBits(1.f, 6)); + UNSCOPED_INFO("evaluate(1) is the end"); + CHECK_THAT(mCurve.evaluate(1.f), ll_test::WithinBits(1.f, 6)); + + UNSCOPED_INFO("added at the left edge"); + CHECK(mCurve.addPoint(0.f, 0.9f) >= 0); + UNSCOPED_INFO("the start is still the start"); + CHECK_THAT(mCurve.getPoints().front().mX, ll_test::WithinBits(0.f, 6)); + UNSCOPED_INFO("and at its height"); + CHECK_THAT(mCurve.getPoints().front().mY, ll_test::WithinBits(0.f, 6)); + UNSCOPED_INFO("interior, one gap inside"); + CHECK_THAT(mCurve.getPoints()[1].mX, ll_test::WithinBits(ALCurveModel::MIN_POINT_GAP, 5)); + UNSCOPED_INFO("evaluate(0) is the start"); + CHECK_THAT(mCurve.evaluate(0.f), ll_test::WithinBits(0.f, 6)); } // Points piled up on the top edge are pushed down, not left coincident: // the separation pass works from both ends. - template<> template<> - void curve_object::test<33>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: points piled up on the top edge are pushed apart below it", "[ALCurveModel]") { mCurve.setPoints(pts({ { 0.f, 0.f }, { 1.f, 0.2f }, { 1.f, 0.4f }, { 1.f, 0.6f }, { 1.f, 1.f } })); const auto& p = mCurve.getPoints(); - ensure_equals("all kept", (S32)p.size(), 5); + REQUIRE((S32)p.size() == 5); for (size_t i = 1; i < p.size(); ++i) { - ensure("separated", p[i].mX - p[i - 1].mX >= ALCurveModel::MIN_POINT_GAP - 1e-6f); + CAPTURE(i); + REQUIRE(p[i].mX - p[i - 1].mX >= ALCurveModel::MIN_POINT_GAP - 1e-6f); } - ensure_approximately_equals("the end is still at 1", p.back().mX, 1.f, 6); - ensure_approximately_equals("evaluate(1) is the end", mCurve.evaluate(1.f), 1.f, 6); + UNSCOPED_INFO("the end is still at 1"); + CHECK_THAT(p.back().mX, ll_test::WithinBits(1.f, 6)); + UNSCOPED_INFO("evaluate(1) is the end"); + CHECK_THAT(mCurve.evaluate(1.f), ll_test::WithinBits(1.f, 6)); std::vector s; mCurve.sample(s, 1025); for (size_t i = 1; i < s.size(); ++i) { - ensure("finite", std::isfinite(s[i])); - ensure("monotone", s[i] >= s[i - 1] - 1e-5f); + CAPTURE(i); + REQUIRE(std::isfinite(s[i])); + REQUIRE(s[i] >= s[i - 1] - 1e-5f); } } - // The setting names are one list: distinct, and channel-shaped. - template<> template<> - void curve_object::test<34>() + TEST_CASE_METHOD(curve_data, "ALCurveModel: the setting names are distinct and named for the curve", "[ALCurveModel]") { for (S32 c = 0; c < ALToneCurveSet::CH_COUNT; ++c) { const std::string name = ALToneCurveSet::settingName((ALToneCurveSet::EChannel)c); - ensure("named for the curve", name.rfind("RenderColorGradeCurve", 0) == 0); + CAPTURE(c); + REQUIRE(name.rfind("RenderColorGradeCurve", 0) == 0); for (S32 d = 0; d < c; ++d) { - ensure("distinct", name != ALToneCurveSet::settingName((ALToneCurveSet::EChannel)d)); + CAPTURE(d); + REQUIRE(name != ALToneCurveSet::settingName((ALToneCurveSet::EChannel)d)); } } - ensure_equals("master", std::string(ALToneCurveSet::settingName(ALToneCurveSet::CH_MASTER)), + CHECK(std::string(ALToneCurveSet::settingName(ALToneCurveSet::CH_MASTER)) == std::string("RenderColorGradeCurveMaster")); } } diff --git a/indra/newview/tests/aldaycyclelandmarks_test.cpp b/indra/newview/tests/aldaycyclelandmarks_test.cpp index 28852cb9e4..8dc2b48b34 100644 --- a/indra/newview/tests/aldaycyclelandmarks_test.cpp +++ b/indra/newview/tests/aldaycyclelandmarks_test.cpp @@ -14,7 +14,7 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../aldaycyclelandmarks.h" @@ -28,7 +28,7 @@ namespace constexpr F32 TWO_PI = 6.28318530718f; } -namespace tut +namespace { struct daycycle_landmarks { @@ -49,115 +49,109 @@ namespace tut } }; - typedef test_group landmark_group; - typedef landmark_group::object landmark_object; - tut::landmark_group lg("ALDayCycleLandmarks"); - // A plain sine puts sunrise at 0, noon at a quarter, sunset at a half and // midnight at three quarters. - template<> template<> - void landmark_object::test<1>() + TEST_CASE_METHOD(daycycle_landmarks, "ALDayCycleLandmarks: a plain sine has its four landmarks a quarter apart from 0", "[ALDayCycleLandmarks]") { const auto marks = ALDayCycleLandmarks::find(sine()); - ensure("has sunrise", marks.has_sunrise); - ensure("has noon", marks.has_noon); - ensure("has sunset", marks.has_sunset); - ensure("has midnight", marks.has_midnight); + CHECK(marks.has_sunrise); + CHECK(marks.has_noon); + CHECK(marks.has_sunset); + CHECK(marks.has_midnight); - ensure("sunrise at 0", apart(marks.sunrise, 0.f) < 0.02f); - ensure("noon at 0.25", apart(marks.noon, 0.25f) < 0.02f); - ensure("sunset at 0.5", apart(marks.sunset, 0.5f) < 0.02f); - ensure("midnight at 0.75", apart(marks.midnight, 0.75f) < 0.02f); + CHECK(apart(marks.sunrise, 0.f) < 0.02f); + CHECK(apart(marks.noon, 0.25f) < 0.02f); + CHECK(apart(marks.sunset, 0.5f) < 0.02f); + CHECK(apart(marks.midnight, 0.75f) < 0.02f); } // Every landmark moves with the cycle. This is the case that a hardcoded // "noon is 0.5" gets wrong, and the reason this code exists. - template<> template<> - void landmark_object::test<2>() + TEST_CASE_METHOD(daycycle_landmarks, "ALDayCycleLandmarks: every landmark moves with the phase of the cycle", "[ALDayCycleLandmarks]") { const F32 phase = 0.3f; const auto marks = ALDayCycleLandmarks::find(sine(phase)); - ensure("sunrise moved", apart(marks.sunrise, phase) < 0.02f); - ensure("noon moved", apart(marks.noon, phase + 0.25f) < 0.02f); - ensure("sunset moved", apart(marks.sunset, std::fmod(phase + 0.5f, 1.f)) < 0.02f); - ensure("midnight moved", apart(marks.midnight, std::fmod(phase + 0.75f, 1.f)) < 0.02f); + CHECK(apart(marks.sunrise, phase) < 0.02f); + CHECK(apart(marks.noon, phase + 0.25f) < 0.02f); + CHECK(apart(marks.sunset, std::fmod(phase + 0.5f, 1.f)) < 0.02f); + CHECK(apart(marks.midnight, std::fmod(phase + 0.75f, 1.f)) < 0.02f); } // A crossing that falls between two samples is interpolated, so the answer // is better than the sample grid rather than snapped to it. - template<> template<> - void landmark_object::test<3>() + TEST_CASE_METHOD(daycycle_landmarks, "ALDayCycleLandmarks: a crossing between two samples is found finer than the grid", "[ALDayCycleLandmarks]") { // Sunrise sits at 0.1, which no 16-sample grid position lands on. const auto marks = ALDayCycleLandmarks::find(sine(0.1f), 16); const F32 grid = 1.f / 16.f; - ensure("has sunrise", marks.has_sunrise); - ensure("beats the grid", apart(marks.sunrise, 0.1f) < grid * 0.5f); + CHECK(marks.has_sunrise); + UNSCOPED_INFO("beats the grid"); + CHECK(apart(marks.sunrise, 0.1f) < grid * 0.5f); } // A sun that never sets has a brightest moment and nothing else: naming a // sunrise there would be inventing one. - template<> template<> - void landmark_object::test<4>() + TEST_CASE_METHOD(daycycle_landmarks, "ALDayCycleLandmarks: a sun that never sets has a noon and nothing else", "[ALDayCycleLandmarks]") { const auto marks = ALDayCycleLandmarks::find( [](F32 p) { return 0.5f + 0.25f * std::sin(TWO_PI * p); }); - ensure("has noon", marks.has_noon); - ensure("no sunrise", !marks.has_sunrise); - ensure("no sunset", !marks.has_sunset); - ensure("no midnight", !marks.has_midnight); - ensure("noon at the peak", apart(marks.noon, 0.25f) < 0.02f); + CHECK(marks.has_noon); + CHECK_FALSE(marks.has_sunrise); + CHECK_FALSE(marks.has_sunset); + CHECK_FALSE(marks.has_midnight); + UNSCOPED_INFO("noon at the peak"); + CHECK(apart(marks.noon, 0.25f) < 0.02f); } // A sun that never rises is the same argument the other way up. - template<> template<> - void landmark_object::test<5>() + TEST_CASE_METHOD(daycycle_landmarks, "ALDayCycleLandmarks: a sun that never rises has a midnight and nothing else", "[ALDayCycleLandmarks]") { const auto marks = ALDayCycleLandmarks::find( [](F32 p) { return -0.5f + 0.25f * std::sin(TWO_PI * p); }); - ensure("has midnight", marks.has_midnight); - ensure("no noon", !marks.has_noon); - ensure("no sunrise", !marks.has_sunrise); - ensure("no sunset", !marks.has_sunset); + CHECK(marks.has_midnight); + CHECK_FALSE(marks.has_noon); + CHECK_FALSE(marks.has_sunrise); + CHECK_FALSE(marks.has_sunset); } // A cycle built from one repeated frame -- which is what the viewer's own // default day cycle actually is -- has no moment to single out. - template<> template<> - void landmark_object::test<6>() + TEST_CASE_METHOD(daycycle_landmarks, "ALDayCycleLandmarks: a cycle of one repeated frame has no landmarks", "[ALDayCycleLandmarks]") { const auto marks = ALDayCycleLandmarks::find([](F32) { return 0.5f; }); - ensure("nothing to find", !marks.any()); + CHECK_FALSE(marks.any()); } // Landmarks are cycle positions, so they stay inside [0, 1) even when the // crossing they came from sits across the seam. - template<> template<> - void landmark_object::test<7>() + TEST_CASE_METHOD(daycycle_landmarks, "ALDayCycleLandmarks: landmarks stay inside the cycle when a crossing is across the seam", "[ALDayCycleLandmarks]") { for (S32 i = 0; i < 20; ++i) { const F32 phase = (F32)i / 20.f; const auto marks = ALDayCycleLandmarks::find(sine(phase)); - - ensure("sunrise in range", marks.sunrise >= 0.f && marks.sunrise < 1.f); - ensure("noon in range", marks.noon >= 0.f && marks.noon < 1.f); - ensure("sunset in range", marks.sunset >= 0.f && marks.sunset < 1.f); - ensure("midnight in range", marks.midnight >= 0.f && marks.midnight < 1.f); + CAPTURE(phase); + + REQUIRE(marks.sunrise >= 0.f); + REQUIRE(marks.sunrise < 1.f); + REQUIRE(marks.noon >= 0.f); + REQUIRE(marks.noon < 1.f); + REQUIRE(marks.sunset >= 0.f); + REQUIRE(marks.sunset < 1.f); + REQUIRE(marks.midnight >= 0.f); + REQUIRE(marks.midnight < 1.f); } } - // Nothing to sample with, nothing to say. - template<> template<> - void landmark_object::test<8>() + TEST_CASE_METHOD(daycycle_landmarks, "ALDayCycleLandmarks: no sampler or too few samples finds nothing", "[ALDayCycleLandmarks]") { - ensure("no sampler", !ALDayCycleLandmarks::find(nullptr).any()); - ensure("too few samples", !ALDayCycleLandmarks::find(sine(), 1).any()); + CHECK_FALSE(ALDayCycleLandmarks::find(nullptr).any()); + CHECK_FALSE(ALDayCycleLandmarks::find(sine(), 1).any()); } } diff --git a/indra/newview/tests/alfarplane_test.cpp b/indra/newview/tests/alfarplane_test.cpp index ada69cd076..404027be15 100644 --- a/indra/newview/tests/alfarplane_test.cpp +++ b/indra/newview/tests/alfarplane_test.cpp @@ -25,7 +25,7 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../alfarplane.h" @@ -37,12 +37,8 @@ #include #include -namespace tut +namespace { - struct alfarplane_data - { - }; - // A shader's source, found from this file so the test reads what the viewer compiles. static std::string shader_source(const std::string& rel) { @@ -60,173 +56,246 @@ namespace tut return at == std::string::npos ? -1.f : std::stof(text.substr(at + key.size())); } - typedef test_group alfarplane_group; - typedef alfarplane_group::object alfarplane_object; - tut::alfarplane_group alfarplane_g("alfarplane"); - // The draw distance consumers see: forward-Z keeps 512 m, reverse-Z reaches MAX_FAR_CLIP. - template<> template<> - void alfarplane_object::test<1>() + TEST_CASE("alfarplane: draw distance caps at 512 m forward-Z and MAX_FAR_CLIP reversed", "[alfarplane]") { - set_test_name("draw distance ceiling"); - ensure_equals("forward ceiling", ALFarPlane::drawDistanceCeiling(false), 512.f); - ensure_equals("reverse ceiling", ALFarPlane::drawDistanceCeiling(true), 2048.f); - ensure_equals("forward caps", ALFarPlane::clampDrawDistance(2048.f, false), 512.f); - ensure_equals("forward keeps lower", ALFarPlane::clampDrawDistance(256.f, false), 256.f); - ensure_equals("reverse keeps 2048", ALFarPlane::clampDrawDistance(2048.f, true), 2048.f); - ensure_equals("reverse caps past MAX_FAR_CLIP", ALFarPlane::clampDrawDistance(4096.f, true), MAX_FAR_CLIP); + UNSCOPED_INFO("forward ceiling"); + CHECK(ALFarPlane::drawDistanceCeiling(false) == 512.f); + UNSCOPED_INFO("reverse ceiling"); + CHECK(ALFarPlane::drawDistanceCeiling(true) == 2048.f); + UNSCOPED_INFO("forward caps"); + CHECK(ALFarPlane::clampDrawDistance(2048.f, false) == 512.f); + UNSCOPED_INFO("forward keeps lower"); + CHECK(ALFarPlane::clampDrawDistance(256.f, false) == 256.f); + UNSCOPED_INFO("reverse keeps 2048"); + CHECK(ALFarPlane::clampDrawDistance(2048.f, true) == 2048.f); + UNSCOPED_INFO("reverse caps past MAX_FAR_CLIP"); + CHECK(ALFarPlane::clampDrawDistance(4096.f, true) == MAX_FAR_CLIP); } // Reverse-Z is infinite; forward-Z and probe captures keep the finite plane, and the debug force moves only the // main view under reverse-Z. - template<> template<> - void alfarplane_object::test<2>() + TEST_CASE("alfarplane: only the reverse-Z main view gets an infinite or forced far plane", "[alfarplane]") { - set_test_name("projection far plane"); using namespace ALFarPlane; - ensure("reverse infinite", isInfinite(projectionFar(true, false))); - ensure_equals("forward", projectionFar(false, false), FINITE_PROJECTION_FAR); - ensure_equals("reverse cube snapshot", projectionFar(true, true), FINITE_PROJECTION_FAR); - ensure_equals("forward cube snapshot", projectionFar(false, true), FINITE_PROJECTION_FAR); - ensure_equals("finite plane is 1024 m", FINITE_PROJECTION_FAR, 1024.f); - ensure("1024 is finite", !isInfinite(1024.f)); - ensure("infinite constant", isInfinite(INFINITE_FAR) && INFINITE_FAR > 0.f); + UNSCOPED_INFO("reverse infinite"); + CHECK(isInfinite(projectionFar(true, false))); + UNSCOPED_INFO("forward"); + CHECK(projectionFar(false, false) == FINITE_PROJECTION_FAR); + UNSCOPED_INFO("reverse cube snapshot"); + CHECK(projectionFar(true, true) == FINITE_PROJECTION_FAR); + UNSCOPED_INFO("forward cube snapshot"); + CHECK(projectionFar(false, true) == FINITE_PROJECTION_FAR); + CHECK(FINITE_PROJECTION_FAR == 1024.f); + CHECK_FALSE(isInfinite(1024.f)); + CHECK(isInfinite(INFINITE_FAR)); + CHECK(INFINITE_FAR > 0.f); - ensure("auto", isInfinite(projectionFar(true, false, FORCE_AUTOMATIC, 2048.f))); - ensure_equals("finite", projectionFar(true, false, FORCE_FINITE, 2048.f), 4096.f); - ensure_equals("finite never under the finite plane", projectionFar(true, false, FORCE_FINITE, 256.f), FINITE_PROJECTION_FAR); - ensure("infinite", isInfinite(projectionFar(true, false, FORCE_INFINITE, 2048.f))); - ensure("unknown mode", isInfinite(projectionFar(true, false, 7, 2048.f))); - ensure_equals("cube snapshot never forced infinite", projectionFar(true, true, FORCE_INFINITE, 2048.f), FINITE_PROJECTION_FAR); - ensure_equals("cube snapshot never forced finite", projectionFar(true, true, FORCE_FINITE, 2048.f), FINITE_PROJECTION_FAR); - ensure_equals("forward ignores infinite", projectionFar(false, false, FORCE_INFINITE, 2048.f), FINITE_PROJECTION_FAR); - ensure_equals("forward ignores finite", projectionFar(false, false, FORCE_FINITE, 2048.f), FINITE_PROJECTION_FAR); + CHECK(isInfinite(projectionFar(true, false, FORCE_AUTOMATIC, 2048.f))); + CHECK(projectionFar(true, false, FORCE_FINITE, 2048.f) == 4096.f); + UNSCOPED_INFO("finite never under the finite plane"); + CHECK(projectionFar(true, false, FORCE_FINITE, 256.f) == FINITE_PROJECTION_FAR); + CHECK(isInfinite(projectionFar(true, false, FORCE_INFINITE, 2048.f))); + UNSCOPED_INFO("unknown mode"); + CHECK(isInfinite(projectionFar(true, false, 7, 2048.f))); + UNSCOPED_INFO("cube snapshot never forced infinite"); + CHECK(projectionFar(true, true, FORCE_INFINITE, 2048.f) == FINITE_PROJECTION_FAR); + UNSCOPED_INFO("cube snapshot never forced finite"); + CHECK(projectionFar(true, true, FORCE_FINITE, 2048.f) == FINITE_PROJECTION_FAR); + UNSCOPED_INFO("forward ignores infinite"); + CHECK(projectionFar(false, false, FORCE_INFINITE, 2048.f) == FINITE_PROJECTION_FAR); + UNSCOPED_INFO("forward ignores finite"); + CHECK(projectionFar(false, false, FORCE_FINITE, 2048.f) == FINITE_PROJECTION_FAR); } // How far terrain and water reach, and from how high water is drawn. - template<> template<> - void alfarplane_object::test<3>() + TEST_CASE("alfarplane: how far terrain and water reach and from how high water shows", "[alfarplane]") { - set_test_name("terrain and water reach"); using namespace ALFarPlane; - ensure_equals("terrain reach under the infinite projection", terrainReach(INFINITE_FAR), 8192.f); - ensure_equals("terrain unbounded on forward-Z", terrainReach(projectionFar(false, false)), 0.f); - ensure_equals("terrain unbounded in cube snapshots", terrainReach(projectionFar(true, true)), 0.f); - ensure_equals("edge water, infinite", edgeWaterStretch(true), 256000.f); - ensure_equals("edge water, finite keeps 2048", edgeWaterStretch(false), 2048.f); - ensure("edge water covers the terrain reach", edgeWaterStretch(true) >= TERRAIN_REACH); + CHECK(terrainReach(INFINITE_FAR) == 8192.f); + UNSCOPED_INFO("terrain unbounded on forward-Z"); + CHECK(terrainReach(projectionFar(false, false)) == 0.f); + UNSCOPED_INFO("terrain unbounded in cube snapshots"); + CHECK(terrainReach(projectionFar(true, true)) == 0.f); + UNSCOPED_INFO("edge water, infinite"); + CHECK(edgeWaterStretch(true) == 256000.f); + UNSCOPED_INFO("edge water, finite keeps 2048"); + CHECK(edgeWaterStretch(false) == 2048.f); + CHECK(edgeWaterStretch(true) >= TERRAIN_REACH); - ensure("finite: below 1024 m", waterVisibleFrom(1000.f, 20.f, FINITE_PROJECTION_FAR)); - ensure("finite: from 1024 m it stops", !waterVisibleFrom(1024.f, 20.f, FINITE_PROJECTION_FAR)); - ensure("finite: a farther plane shows it from higher", waterVisibleFrom(2000.f, 20.f, 4096.f)); - ensure("finite: up to that plane", !waterVisibleFrom(4096.f, 20.f, 4096.f)); - ensure("infinite: 2 km up still shows", waterVisibleFrom(2000.f, 20.f, INFINITE_FAR)); - ensure("infinite: within reach above the water", waterVisibleFrom(20.f + EDGE_WATER_STRETCH - 1.f, 20.f, INFINITE_FAR)); - ensure("infinite: past reach above the water", !waterVisibleFrom(20.f + EDGE_WATER_STRETCH, 20.f, INFINITE_FAR)); + UNSCOPED_INFO("finite: below 1024 m"); + CHECK(waterVisibleFrom(1000.f, 20.f, FINITE_PROJECTION_FAR)); + UNSCOPED_INFO("finite: from 1024 m it stops"); + CHECK_FALSE(waterVisibleFrom(1024.f, 20.f, FINITE_PROJECTION_FAR)); + UNSCOPED_INFO("finite: a farther plane shows it from higher"); + CHECK(waterVisibleFrom(2000.f, 20.f, 4096.f)); + UNSCOPED_INFO("finite: up to that plane"); + CHECK_FALSE(waterVisibleFrom(4096.f, 20.f, 4096.f)); + UNSCOPED_INFO("infinite: 2 km up still shows"); + CHECK(waterVisibleFrom(2000.f, 20.f, INFINITE_FAR)); + UNSCOPED_INFO("infinite: within reach above the water"); + CHECK(waterVisibleFrom(20.f + EDGE_WATER_STRETCH - 1.f, 20.f, INFINITE_FAR)); + UNSCOPED_INFO("infinite: past reach above the water"); + CHECK_FALSE(waterVisibleFrom(20.f + EDGE_WATER_STRETCH, 20.f, INFINITE_FAR)); } // Depth reconstruction: the far corners an infinite projection unprojects, and the floors the shaders carry. - template<> template<> - void alfarplane_object::test<4>() + TEST_CASE("alfarplane: the far window depth and the reconstruction floor in the shaders", "[alfarplane]") { - set_test_name("depth reconstruction"); using namespace ALFarPlane; - ensure_equals("infinite", frustumFarWindowDepth(true, 0.1f, 0.f), 0.1f / MAX_RECONSTRUCT_DISTANCE); - ensure("infinite depth unprojects to MAX_RECONSTRUCT_DISTANCE", fabsf(0.1f / frustumFarWindowDepth(true, 0.1f, 0.f) - MAX_RECONSTRUCT_DISTANCE) < 1.f); - ensure_equals("finite, reverse", frustumFarWindowDepth(false, 0.1f, 0.f), 0.f); - ensure_equals("finite, forward", frustumFarWindowDepth(false, 0.1f, 1.f), 1.f); + UNSCOPED_INFO("infinite"); + CHECK(frustumFarWindowDepth(true, 0.1f, 0.f) == 0.1f / MAX_RECONSTRUCT_DISTANCE); + UNSCOPED_INFO("infinite depth unprojects to MAX_RECONSTRUCT_DISTANCE"); + CHECK(fabsf(0.1f / frustumFarWindowDepth(true, 0.1f, 0.f) - MAX_RECONSTRUCT_DISTANCE) < 1.f); + UNSCOPED_INFO("finite, reverse"); + CHECK(frustumFarWindowDepth(false, 0.1f, 0.f) == 0.f); + UNSCOPED_INFO("finite, forward"); + CHECK(frustumFarWindowDepth(false, 0.1f, 1.f) == 1.f); // The sky keeps the cleared depth, 0 under reverse-Z. Anything drawn stores near / distance, which must stay // a normal float out to the reconstruction floor so no geometry rounds onto the sky. - ensure("reconstruction floor past the reach", MAX_RECONSTRUCT_DISTANCE > TERRAIN_REACH); - ensure("geometry at the floor stores a normal depth", MIN_NEAR_PLANE / MAX_RECONSTRUCT_DISTANCE >= std::numeric_limits::min()); + CHECK(MAX_RECONSTRUCT_DISTANCE > TERRAIN_REACH); + UNSCOPED_INFO("geometry at the floor stores a normal depth"); + CHECK(MIN_NEAR_PLANE / MAX_RECONSTRUCT_DISTANCE >= std::numeric_limits::min()); // The floors the shaders actually carry, read from their sources. const F32 floor = 1.f / MAX_RECONSTRUCT_DISTANCE; const std::string util = shader_source("class1/deferred/deferredUtil.glsl"); - ensure("deferredUtil read", !util.empty()); - ensure_equals("deferredUtil floor", literal_after(util, "#define RECONSTRUCT_W_FLOOR "), floor); - ensure_equals("aoUtil floor", literal_after(shader_source("class1/deferred/aoUtil.glsl"), "pos.w = max(pos.w, "), floor); - ensure_equals("cofF floor", literal_after(shader_source("class1/deferred/cofF.glsl"), "p.z/max(p.w, "), floor); + UNSCOPED_INFO("deferredUtil read"); + REQUIRE_FALSE(util.empty()); + UNSCOPED_INFO("deferredUtil floor"); + CHECK(literal_after(util, "#define RECONSTRUCT_W_FLOOR ") == floor); + CHECK(literal_after(shader_source("class1/deferred/aoUtil.glsl"), "pos.w = max(pos.w, ") == floor); + CHECK(literal_after(shader_source("class1/deferred/cofF.glsl"), "p.z/max(p.w, ") == floor); } // The sky is the cleared far depth and nothing else: every pass that tells sky from geometry tests for exactly // that value, so no distance, projection or pin enters the test. - template<> template<> - void alfarplane_object::test<5>() + TEST_CASE("alfarplane: every pass tells sky from geometry by the cleared depth alone", "[alfarplane]") { - set_test_name("sky is the cleared depth"); const std::string haze = shader_source("class3/deferred/hazeF.glsl"); const std::string haze_v = shader_source("class1/deferred/hazeV.glsl"); - ensure("haze drawn on the far plane, reversed", haze_v.find("gl_Position = vec4(position.xy, 0.0, 1.0);") != std::string::npos); - ensure("haze drawn on the far plane, forward", haze_v.find("gl_Position = vec4(position.xy, 1.0, 1.0);") != std::string::npos); + UNSCOPED_INFO("haze drawn on the far plane, reversed"); + REQUIRE(haze_v.find("gl_Position = vec4(position.xy, 0.0, 1.0);") != std::string::npos); + UNSCOPED_INFO("haze drawn on the far plane, forward"); + REQUIRE(haze_v.find("gl_Position = vec4(position.xy, 1.0, 1.0);") != std::string::npos); // The lighting passes draw there too: the light map's passes, the lights and the soften pass's lit program in // front of it, the soften pass's sky program on it. for (const char* path : { "class2/deferred/softenLightV.glsl", "class3/deferred/multiPointLightV.glsl", "class2/deferred/sunLightV.glsl", "class1/deferred/blurLightV.glsl" }) { const std::string v = shader_source(path); - ensure(std::string(path) + " drawn on the far plane, reversed", v.find("gl_Position = vec4(position.xy, 0.0, 1.0);") != std::string::npos); - ensure(std::string(path) + " drawn on the far plane, forward", v.find("gl_Position = vec4(position.xy, 1.0, 1.0);") != std::string::npos); + UNSCOPED_INFO(path << " drawn on the far plane, reversed"); + REQUIRE(v.find("gl_Position = vec4(position.xy, 0.0, 1.0);") != std::string::npos); + UNSCOPED_INFO(path << " drawn on the far plane, forward"); + REQUIRE(v.find("gl_Position = vec4(position.xy, 1.0, 1.0);") != std::string::npos); } const std::string soften = shader_source("class3/deferred/softenLightF.glsl"); - ensure("the sky's soften program lights nothing", soften.find("writeColor(skyColor(getGBuffer(tc)));") != std::string::npos); - ensure("haze takes geometry where it is", haze.find("= getPositionWithDepth(tc, depth);") != std::string::npos); - ensure("void water fog keys on the far depth", shader_source("class3/deferred/waterHazeF.glsl").find("if (isFarDepth(depth))") != std::string::npos); + UNSCOPED_INFO("the sky's soften program lights nothing"); + REQUIRE(soften.find("writeColor(skyColor(getGBuffer(tc)));") != std::string::npos); + UNSCOPED_INFO("haze takes geometry where it is"); + REQUIRE(haze.find("= getPositionWithDepth(tc, depth);") != std::string::npos); + UNSCOPED_INFO("void water fog keys on the far depth"); + REQUIRE(shader_source("class3/deferred/waterHazeF.glsl").find("if (isFarDepth(depth))") != std::string::npos); const std::string flare = shader_source("class1/alchemy/lensFlareStateF.glsl"); - ensure("flare sky is the far plane, reversed", flare.find("return d <= 0.0 ? 1.0 : 0.0;") != std::string::npos); - ensure("flare sky is the far plane, forward", flare.find("return d >= 1.0 ? 1.0 : 0.0;") != std::string::npos); + UNSCOPED_INFO("flare sky is the far plane, reversed"); + REQUIRE(flare.find("return d <= 0.0 ? 1.0 : 0.0;") != std::string::npos); + UNSCOPED_INFO("flare sky is the far plane, forward"); + REQUIRE(flare.find("return d >= 1.0 ? 1.0 : 0.0;") != std::string::npos); // Opaque geometry in the haze pass takes the in-scatter blended geometry takes in its own shading, and the // water surface alone converges on the sky behind it. - ensure("the haze pass's in-scatter is alpha's", haze.find("atmosFragLighting(vec3(0), additive, atten)") != std::string::npos); - ensure("water converges on the sky behind", haze.find("if (on_water && skyBehindWeight > 0.0)") != std::string::npos); - ensure("the sky behind is sampled for water", haze.find("texture(skyBehindMap, tc)") != std::string::npos); + UNSCOPED_INFO("the haze pass's in-scatter is alpha's"); + REQUIRE(haze.find("atmosFragLighting(vec3(0), additive, atten)") != std::string::npos); + UNSCOPED_INFO("water converges on the sky behind"); + REQUIRE(haze.find("if (on_water && skyBehindWeight > 0.0)") != std::string::npos); + UNSCOPED_INFO("the sky behind is sampled for water"); + REQUIRE(haze.find("texture(skyBehindMap, tc)") != std::string::npos); // The farthest geometry: a top corner of the edge water's extent, from as high above it as the water shows. const F64 edge = (F64)ALFarPlane::edgeWaterStretch(true) + MAX_FAR_CLIP; const F64 farthest = sqrt(2.0 * edge * edge + (F64)ALFarPlane::EDGE_WATER_STRETCH * ALFarPlane::EDGE_WATER_STRETCH); - ensure("farthest water inside the floor", farthest < MAX_RECONSTRUCT_DISTANCE); - ensure("farthest water stores a normal depth", (F32)(MIN_NEAR_PLANE / farthest) >= std::numeric_limits::min()); + CHECK(farthest < MAX_RECONSTRUCT_DISTANCE); + UNSCOPED_INFO("farthest water stores a normal depth"); + CHECK((F32)(MIN_NEAR_PLANE / farthest) >= std::numeric_limits::min()); } // Water's far terms: the old ones under a finite projection; under an infinite one the waves follow the surface // to the rim's farthest corner and the water fades over the edge water stretch. - template<> template<> - void alfarplane_object::test<6>() + TEST_CASE("alfarplane: water's wave clamp and edge fade under each projection", "[alfarplane]") { - set_test_name("water far terms"); using namespace ALFarPlane; const LLVector2 lo(-1000.f, -2000.f); const LLVector2 hi(3000.f, 500.f); const WaterFar finite = waterFar(FINITE_PROJECTION_FAR, true, lo, hi); - ensure_equals("finite wave clamp", finite.mWaveClamp, 2560.f); - ensure_equals("finite no edge fade", finite.mEdgeFade, 0.f); + CHECK(finite.mWaveClamp == 2560.f); + CHECK(finite.mEdgeFade == 0.f); const WaterFar infinite = waterFar(INFINITE_FAR, true, lo, hi); - ensure("wave clamp at the farthest rim corner", fabsf(infinite.mWaveClamp - sqrtf(3000.f * 3000.f + 2000.f * 2000.f)) < 0.01f); - ensure_equals("edge fade over the stretch", infinite.mEdgeFade, EDGE_WATER_STRETCH); + UNSCOPED_INFO("wave clamp at the farthest rim corner"); + CHECK(fabsf(infinite.mWaveClamp - sqrtf(3000.f * 3000.f + 2000.f * 2000.f)) < 0.01f); + CHECK(infinite.mEdgeFade == EDGE_WATER_STRETCH); const WaterFar rimless = waterFar(INFINITE_FAR, false, LLVector2(), LLVector2()); - ensure_equals("no rim, no fade", rimless.mEdgeFade, 0.f); - ensure("no rim, waves past the regions", rimless.mWaveClamp >= EDGE_WATER_STRETCH); + CHECK(rimless.mEdgeFade == 0.f); + UNSCOPED_INFO("no rim, waves past the regions"); + CHECK(rimless.mWaveClamp >= EDGE_WATER_STRETCH); const std::string water_f = shader_source("class3/environment/waterF.glsl"); const std::string haze = shader_source("class3/deferred/waterHazeF.glsl"); - ensure("sky behind water is held past the surface", haze.find("entry + VOID_WATER_FOG_DEPTH") != std::string::npos); + UNSCOPED_INFO("sky behind water is held past the surface"); + REQUIRE(haze.find("entry + VOID_WATER_FOG_DEPTH") != std::string::npos); const std::string water_v = shader_source("class1/environment/waterV.glsl"); - ensure("wave clamp from the uniform", water_v.find("min(d, waterFar.x)") != std::string::npos); - ensure("no literal wave clamp", water_v.find("min(d, 2560.0)") == std::string::npos); + UNSCOPED_INFO("wave clamp from the uniform"); + REQUIRE(water_v.find("min(d, waterFar.x)") != std::string::npos); + UNSCOPED_INFO("no literal wave clamp"); + REQUIRE(water_v.find("min(d, 2560.0)") == std::string::npos); // The per-fragment rebuild scrolls by the offsets waterV takes from the CPU, which sets no wave direction or time. for (const char* scroll : { "+ bigWaveScroll;", "+ littleWaveScroll.xy;", "+ littleWaveScroll.zw;" }) { - ensure(std::string("waterV scrolls by ") + scroll, water_v.find(scroll) != std::string::npos); - ensure(std::string("rebuilt waves scroll by ") + scroll, water_f.find(scroll) != std::string::npos); + UNSCOPED_INFO("waterV scrolls by " << scroll); + REQUIRE(water_v.find(scroll) != std::string::npos); + UNSCOPED_INFO("rebuilt waves scroll by " << scroll); + REQUIRE(water_f.find(scroll) != std::string::npos); + } + } + + // The far plane overlay: nearer-first pairs out to 100 km on distinct bearings, each farther column behind its + // partner and overlapping it on screen, and every column the same angular size. + TEST_CASE("alfarplane: overlay columns pair up out to 100 km at one angular size", "[alfarplane]") + { + const std::vector& columns = ALFarPlane::overlayColumns(); + REQUIRE(columns.size() >= 2); + REQUIRE(columns.size() % 2 == 0); + REQUIRE(columns[columns.size() - 2].mDistance == 100000.f); + for (size_t i = 0; i < columns.size(); i += 2) + { + const ALFarPlane::OverlayColumn& n = columns[i]; + const ALFarPlane::OverlayColumn& f = columns[i + 1]; + CAPTURE(n.mDistance); + REQUIRE(n.mNearer); + REQUIRE_FALSE(f.mNearer); + REQUIRE(f.mDistance > n.mDistance); + REQUIRE(f.mBearing == n.mBearing); + // On screen the farther column's near edge sits inside the nearer column. + const F32 f_left = (f.mOffset - f.mHalfWidth) / f.mDistance; + const F32 n_right = (n.mOffset + n.mHalfWidth) / n.mDistance; + const F32 n_left = (n.mOffset - n.mHalfWidth) / n.mDistance; + { + INFO("the farther column overlaps the nearer on screen"); + REQUIRE(f_left > n_left); + REQUIRE(f_left < n_right); + } + UNSCOPED_INFO("every column the same angular size"); + REQUIRE(fabsf(n.mHeight / n.mDistance - columns[0].mHeight / columns[0].mDistance) < 1e-6f); + if (i > 0) + { + REQUIRE(n.mBearing != columns[i - 2].mBearing); + REQUIRE(n.mDistance > columns[i - 2].mDistance); + } } } // The eye-to-sky rotation: a level camera's forward, up and right land on the sky's (north, up, east) for its heading, // and any view's eye directions come back as toLightNorm's swizzle of the agent directions it rotated. - template<> template<> - void alfarplane_object::test<8>() + TEST_CASE("alfarplane: eyeToSkyFrame turns eye directions into the sky's frame", "[alfarplane]") { - set_test_name("eye to sky frame"); const auto apply = [](const std::array& m, const LLVector3& v) { return LLVector3(m[0] * v.mV[0] + m[3] * v.mV[1] + m[6] * v.mV[2], @@ -241,17 +310,22 @@ namespace tut north.setIdentity(); north.setRows(LLVector4a(1.f, 0.f, 0.f), LLVector4a(0.f, 0.f, -1.f), LLVector4a(0.f, 1.f, 0.f)); const std::array n = ALFarPlane::eyeToSkyFrame(north); - ensure("facing north, forward is north", close_to(apply(n, LLVector3(0.f, 0.f, -1.f)), LLVector3(1.f, 0.f, 0.f))); - ensure("facing north, up is up", close_to(apply(n, LLVector3(0.f, 1.f, 0.f)), LLVector3(0.f, 1.f, 0.f))); - ensure("facing north, right is east", close_to(apply(n, LLVector3(1.f, 0.f, 0.f)), LLVector3(0.f, 0.f, 1.f))); + UNSCOPED_INFO("facing north, forward is north"); + CHECK(close_to(apply(n, LLVector3(0.f, 0.f, -1.f)), LLVector3(1.f, 0.f, 0.f))); + UNSCOPED_INFO("facing north, up is up"); + CHECK(close_to(apply(n, LLVector3(0.f, 1.f, 0.f)), LLVector3(0.f, 1.f, 0.f))); + UNSCOPED_INFO("facing north, right is east"); + CHECK(close_to(apply(n, LLVector3(1.f, 0.f, 0.f)), LLVector3(0.f, 0.f, 1.f))); // Facing east: eye right is south, eye -z is east. LLMatrix4a east; east.setIdentity(); east.setRows(LLVector4a(0.f, 0.f, -1.f), LLVector4a(-1.f, 0.f, 0.f), LLVector4a(0.f, 1.f, 0.f)); const std::array e = ALFarPlane::eyeToSkyFrame(east); - ensure("facing east, forward is east", close_to(apply(e, LLVector3(0.f, 0.f, -1.f)), LLVector3(0.f, 0.f, 1.f))); - ensure("facing east, right is south", close_to(apply(e, LLVector3(1.f, 0.f, 0.f)), LLVector3(-1.f, 0.f, 0.f))); + UNSCOPED_INFO("facing east, forward is east"); + CHECK(close_to(apply(e, LLVector3(0.f, 0.f, -1.f)), LLVector3(0.f, 0.f, 1.f))); + UNSCOPED_INFO("facing east, right is south"); + CHECK(close_to(apply(e, LLVector3(1.f, 0.f, 0.f)), LLVector3(-1.f, 0.f, 0.f))); // A pitched, rolled, yawed view: what the pipeline rotates into eye space (as it does the sun's direction) // comes back as the sky frame's swizzle of the agent direction. @@ -262,49 +336,25 @@ namespace tut LLVector4a eye; view.rotate(LLVector4a(agent.mV[0], agent.mV[1], agent.mV[2], 0.f), eye); const LLVector3 sky = apply(v, LLVector3(eye.getF32ptr())); - ensure("agent direction back as (north, up, east)", close_to(sky, LLVector3(agent.mV[1], agent.mV[2], agent.mV[0]))); + UNSCOPED_INFO("agent direction back as (north, up, east)"); + REQUIRE(close_to(sky, LLVector3(agent.mV[1], agent.mV[2], agent.mV[0]))); } // WindLight's haze takes the rotation from the Environment block, for every pass that hazes geometry, and its // callers hand it eye space. const std::string funcs = shader_source("class1/windlight/atmosphericsFuncs.glsl"); - ensure("haze position into the sky's frame", funcs.find("vec3 rel_pos = eyeToSky * inPositionEye;") != std::string::npos); - ensure("haze light into the sky's frame", funcs.find("light_dir = eyeToSky * light_dir;") != std::string::npos); + UNSCOPED_INFO("haze position into the sky's frame"); + REQUIRE(funcs.find("vec3 rel_pos = eyeToSky * inPositionEye;") != std::string::npos); + UNSCOPED_INFO("haze light into the sky's frame"); + REQUIRE(funcs.find("light_dir = eyeToSky * light_dir;") != std::string::npos); // Only rising rays leave the haze, as the dome's do; a falling ray keeps its whole length. - ensure("altitude limit on rising rays alone", funcs.find("float h = max(rel_pos.y, 0.0) / max(max_y, 1.0);") != std::string::npos); - ensure("no limit on falling rays", funcs.find("abs(rel_pos.y)") == std::string::npos); - ensure("the rotation is in the Environment block", shader_source("class1/deferred/environmentBlock.glsl").find("mat3 eyeToSky;") != std::string::npos); - ensure("the haze pass hands over eye space", shader_source("class3/deferred/hazeF.glsl").find("eyeToSky") == std::string::npos); - } - - // The far plane overlay: nearer-first pairs out to 100 km on distinct bearings, each farther column behind its - // partner and overlapping it on screen, and every column the same angular size. - template<> template<> - void alfarplane_object::test<7>() - { - set_test_name("overlay columns"); - const std::vector& columns = ALFarPlane::overlayColumns(); - ensure("pairs", columns.size() >= 2 && columns.size() % 2 == 0); - ensure_equals("reaches 100 km", columns[columns.size() - 2].mDistance, 100000.f); - for (size_t i = 0; i < columns.size(); i += 2) - { - const ALFarPlane::OverlayColumn& n = columns[i]; - const ALFarPlane::OverlayColumn& f = columns[i + 1]; - const std::string at = std::to_string((S32)n.mDistance) + " m"; - ensure("nearer first at " + at, n.mNearer && !f.mNearer); - ensure("farther behind at " + at, f.mDistance > n.mDistance); - ensure("same bearing at " + at, f.mBearing == n.mBearing); - // On screen the farther column's near edge sits inside the nearer column. - const F32 f_left = (f.mOffset - f.mHalfWidth) / f.mDistance; - const F32 n_right = (n.mOffset + n.mHalfWidth) / n.mDistance; - const F32 n_left = (n.mOffset - n.mHalfWidth) / n.mDistance; - ensure("overlap at " + at, f_left > n_left && f_left < n_right); - ensure("same angle at " + at, fabsf(n.mHeight / n.mDistance - columns[0].mHeight / columns[0].mDistance) < 1e-6f); - if (i > 0) - { - ensure("own bearing at " + at, n.mBearing != columns[i - 2].mBearing); - ensure("receding at " + at, n.mDistance > columns[i - 2].mDistance); - } - } + UNSCOPED_INFO("altitude limit on rising rays alone"); + REQUIRE(funcs.find("float h = max(rel_pos.y, 0.0) / max(max_y, 1.0);") != std::string::npos); + UNSCOPED_INFO("no limit on falling rays"); + REQUIRE(funcs.find("abs(rel_pos.y)") == std::string::npos); + UNSCOPED_INFO("the rotation is in the Environment block"); + REQUIRE(shader_source("class1/deferred/environmentBlock.glsl").find("mat3 eyeToSky;") != std::string::npos); + UNSCOPED_INFO("the haze pass hands over eye space"); + REQUIRE(shader_source("class3/deferred/hazeF.glsl").find("eyeToSky") == std::string::npos); } } diff --git a/indra/newview/tests/alfeedback_test.cpp b/indra/newview/tests/alfeedback_test.cpp index c29ccbe9c6..938904eb43 100644 --- a/indra/newview/tests/alfeedback_test.cpp +++ b/indra/newview/tests/alfeedback_test.cpp @@ -29,7 +29,7 @@ #include "llsd.h" #include "llsdjson.h" -#include "../test/lltut.h" +#include #include @@ -59,128 +59,119 @@ namespace } } -namespace tut +namespace { - struct alfeedback_data - { - }; - - typedef test_group alfeedback_group; - typedef alfeedback_group::object object; - alfeedback_group alfeedbackgrp("alfeedback"); - - template<> template<> - void object::test<1>() + TEST_CASE("alfeedback: a sentry.io DSN sends to its project's envelope endpoint", "[alfeedback]") { - set_test_name("a sentry.io DSN sends to its project's envelope endpoint"); const auto endpoint = ALFeedback::endpointFromDsn("https://0123abcd@o42.ingest.sentry.io/4501"); - ensure("parsed", endpoint.has_value()); - ensure_equals(endpoint->url, "https://o42.ingest.sentry.io/api/4501/envelope/"); - ensure_equals(endpoint->publicKey, "0123abcd"); + REQUIRE(endpoint.has_value()); + CHECK(endpoint->url == "https://o42.ingest.sentry.io/api/4501/envelope/"); + CHECK(endpoint->publicKey == "0123abcd"); } - template<> template<> - void object::test<2>() + TEST_CASE("alfeedback: a self-hosted DSN keeps its port and path and drops an old secret", "[alfeedback]") { - set_test_name("a self-hosted DSN keeps its port and path, and drops an old secret"); const auto endpoint = ALFeedback::endpointFromDsn("https://key:secret@sentry.example.com:9000/prefix/7/"); - ensure("parsed", endpoint.has_value()); - ensure_equals(endpoint->url, "https://sentry.example.com:9000/prefix/api/7/envelope/"); - ensure_equals(endpoint->publicKey, "key"); + REQUIRE(endpoint.has_value()); + CHECK(endpoint->url == "https://sentry.example.com:9000/prefix/api/7/envelope/"); + CHECK(endpoint->publicKey == "key"); } - template<> template<> - void object::test<3>() + TEST_CASE("alfeedback: a DSN missing a part is refused", "[alfeedback]") { - set_test_name("a DSN missing a part is refused"); - ensure("no scheme", !ALFeedback::endpointFromDsn("key@host/1")); - ensure("another scheme", !ALFeedback::endpointFromDsn("ftp://key@host/1")); - ensure("no key", !ALFeedback::endpointFromDsn("https://host/1")); - ensure("empty key", !ALFeedback::endpointFromDsn("https://@host/1")); - ensure("no project", !ALFeedback::endpointFromDsn("https://key@host")); - ensure("empty host", !ALFeedback::endpointFromDsn("https://key@/1")); - ensure("empty", !ALFeedback::endpointFromDsn("")); + UNSCOPED_INFO("no scheme"); + REQUIRE_FALSE(ALFeedback::endpointFromDsn("key@host/1")); + UNSCOPED_INFO("another scheme"); + REQUIRE_FALSE(ALFeedback::endpointFromDsn("ftp://key@host/1")); + UNSCOPED_INFO("no key"); + REQUIRE_FALSE(ALFeedback::endpointFromDsn("https://host/1")); + UNSCOPED_INFO("empty key"); + REQUIRE_FALSE(ALFeedback::endpointFromDsn("https://@host/1")); + UNSCOPED_INFO("no project"); + REQUIRE_FALSE(ALFeedback::endpointFromDsn("https://key@host")); + UNSCOPED_INFO("empty host"); + REQUIRE_FALSE(ALFeedback::endpointFromDsn("https://key@/1")); + REQUIRE_FALSE(ALFeedback::endpointFromDsn("")); } - template<> template<> - void object::test<4>() + TEST_CASE("alfeedback: the auth header names the protocol version and the client and key", "[alfeedback]") { - set_test_name("the auth header names the protocol, the client and the key"); - ensure_equals(ALFeedback::authHeader("0123abcd", "alchemy-feedback/26.4.0"), + CHECK(ALFeedback::authHeader("0123abcd", "alchemy-feedback/26.4.0") == "Sentry sentry_version=7, sentry_client=alchemy-feedback/26.4.0, sentry_key=0123abcd"); } - template<> template<> - void object::test<5>() + TEST_CASE("alfeedback: settings named for keys or passwords or secrets or emails stay private", "[alfeedback]") { - set_test_name("keys, passwords and tokens stay out of the settings sent"); - ensure("translation key", ALFeedback::privateSetting("DeepLTranslateAPIKey")); - ensure("any case", ALFeedback::privateSetting("someapikey")); - ensure("password", ALFeedback::privateSetting("RememberPassword")); - ensure("token", ALFeedback::privateSetting("ServiceToken")); - ensure("secret", ALFeedback::privateSetting("ClientSecret")); - ensure("an address", ALFeedback::privateSetting("AlchemyFeedbackEmail")); - ensure("an ordinary setting goes", !ALFeedback::privateSetting("RenderQualityPerformance")); - ensure("a key binding goes", !ALFeedback::privateSetting("ArrowKeysAlwaysMove")); + CHECK(ALFeedback::privateSetting("DeepLTranslateAPIKey")); + UNSCOPED_INFO("any case"); + CHECK(ALFeedback::privateSetting("someapikey")); + CHECK(ALFeedback::privateSetting("RememberPassword")); + CHECK(ALFeedback::privateSetting("ServiceToken")); + CHECK(ALFeedback::privateSetting("ClientSecret")); + UNSCOPED_INFO("an address"); + CHECK(ALFeedback::privateSetting("AlchemyFeedbackEmail")); + UNSCOPED_INFO("an ordinary setting goes"); + CHECK_FALSE(ALFeedback::privateSetting("RenderQualityPerformance")); + UNSCOPED_INFO("a key binding goes"); + CHECK_FALSE(ALFeedback::privateSetting("ArrowKeysAlwaysMove")); } - template<> template<> - void object::test<6>() + TEST_CASE("alfeedback: an email is checked for its shape only", "[alfeedback]") { - set_test_name("an email is checked for its shape only"); - ensure("plain", ALFeedback::plausibleEmail("rye@alchemyviewer.org")); - ensure("subdomain", ALFeedback::plausibleEmail("a.b+c@mail.example.co.uk")); - ensure("no at", !ALFeedback::plausibleEmail("rye.alchemyviewer.org")); - ensure("two ats", !ALFeedback::plausibleEmail("a@b@c.d")); - ensure("no local part", !ALFeedback::plausibleEmail("@example.com")); - ensure("no dot", !ALFeedback::plausibleEmail("rye@localhost")); - ensure("dot last", !ALFeedback::plausibleEmail("rye@example.")); - ensure("dot first", !ALFeedback::plausibleEmail("rye@.com")); - ensure("space", !ALFeedback::plausibleEmail("r ye@example.com")); - ensure("empty", !ALFeedback::plausibleEmail("")); + UNSCOPED_INFO("plain"); + CHECK(ALFeedback::plausibleEmail("rye@alchemyviewer.org")); + UNSCOPED_INFO("subdomain"); + CHECK(ALFeedback::plausibleEmail("a.b+c@mail.example.co.uk")); + UNSCOPED_INFO("no at"); + CHECK_FALSE(ALFeedback::plausibleEmail("rye.alchemyviewer.org")); + UNSCOPED_INFO("two ats"); + CHECK_FALSE(ALFeedback::plausibleEmail("a@b@c.d")); + UNSCOPED_INFO("no local part"); + CHECK_FALSE(ALFeedback::plausibleEmail("@example.com")); + UNSCOPED_INFO("no dot"); + CHECK_FALSE(ALFeedback::plausibleEmail("rye@localhost")); + UNSCOPED_INFO("dot last"); + CHECK_FALSE(ALFeedback::plausibleEmail("rye@example.")); + UNSCOPED_INFO("dot first"); + CHECK_FALSE(ALFeedback::plausibleEmail("rye@.com")); + UNSCOPED_INFO("space"); + CHECK_FALSE(ALFeedback::plausibleEmail("r ye@example.com")); + CHECK_FALSE(ALFeedback::plausibleEmail("")); } - template<> template<> - void object::test<7>() + TEST_CASE("alfeedback: a short log goes whole", "[alfeedback]") { - set_test_name("a short log goes whole"); - ensure_equals(std::string(ALFeedback::logTail("one\ntwo\n", 64)), "one\ntwo\n"); + CHECK(std::string(ALFeedback::logTail("one\ntwo\n", 64)) == "one\ntwo\n"); } - template<> template<> - void object::test<8>() + TEST_CASE("alfeedback: a long log's tail starts at a line", "[alfeedback]") { - set_test_name("a long log's tail starts at a line"); const std::string log = "first line\nsecond line\nthird\n"; - ensure_equals("mid-line cut moves to the next line", std::string(ALFeedback::logTail(log, 15)), "third\n"); - ensure_equals("a cut on a line start stays", std::string(ALFeedback::logTail(log, 18)), + UNSCOPED_INFO("mid-line cut moves to the next line"); + CHECK(std::string(ALFeedback::logTail(log, 15)) == "third\n"); + UNSCOPED_INFO("a cut on a line start stays"); + CHECK(std::string(ALFeedback::logTail(log, 18)) == "second line\nthird\n"); } - template<> template<> - void object::test<9>() + TEST_CASE("alfeedback: a tail inside one long line never starts inside a character", "[alfeedback]") { - set_test_name("a tail inside one long line never starts inside a character"); // Three two-byte characters; five bytes from the end cuts the first. const std::string log = "\xC3\xA9\xC3\xA9\xC3\xA9"; const std::string tail(ALFeedback::logTail(log, 5)); - ensure_equals(tail, "\xC3\xA9\xC3\xA9"); + CHECK(tail == "\xC3\xA9\xC3\xA9"); } - template<> template<> - void object::test<10>() + TEST_CASE("alfeedback: an event id is 32 lower-case hex digits and new each time", "[alfeedback]") { - set_test_name("an event id is 32 lower-case hex digits, new each time"); const std::string id = ALFeedback::newEventId(); - ensure_equals("length", id.size(), 32u); - ensure("hex", id.find_first_not_of("0123456789abcdef") == std::string::npos); - ensure("new", id != ALFeedback::newEventId()); + REQUIRE(id.size() == 32u); + REQUIRE(id.find_first_not_of("0123456789abcdef") == std::string::npos); + CHECK(id != ALFeedback::newEventId()); } - template<> template<> - void object::test<11>() + TEST_CASE("alfeedback: the feedback event carries the message as its feedback context", "[alfeedback]") { - set_test_name("the feedback event carries the message as its feedback context"); ALFeedback::Message message; message.kind = ALFeedback::Kind::Idea; message.text = "More knobs"; @@ -200,62 +191,60 @@ namespace tut context.tags = { { "run_id", "run" }, { "empty", "" } }; const LLSD event = ALFeedback::feedbackEvent("feedc0de", "2026-10-09T12:00:00Z", message, context); - ensure_equals("id", event["event_id"].asString(), "feedc0de"); - ensure_equals("timestamp", event["timestamp"].asString(), "2026-10-09T12:00:00Z"); - ensure_equals("level", event["level"].asString(), "info"); - ensure_equals("release", event["release"].asString(), "alchemy@26.4.0+64033"); - ensure_equals("environment", event["environment"].asString(), "Alchemy Test"); - ensure_equals("dist", event["dist"].asString(), "64033"); - ensure_equals("user id", event["user"]["id"].asString(), context.userId); - ensure_equals("user name", event["user"]["username"].asString(), "rye.resident"); - ensure_equals("kind", event["tags"]["feedback_kind"].asString(), "idea"); - ensure_equals("tag", event["tags"]["run_id"].asString(), "run"); - ensure("an empty tag is left out", !event["tags"].has("empty")); + CHECK(event["event_id"].asString() == "feedc0de"); + CHECK(event["timestamp"].asString() == "2026-10-09T12:00:00Z"); + CHECK(event["level"].asString() == "info"); + CHECK(event["release"].asString() == "alchemy@26.4.0+64033"); + CHECK(event["environment"].asString() == "Alchemy Test"); + CHECK(event["dist"].asString() == "64033"); + CHECK(event["user"]["id"].asString() == context.userId); + CHECK(event["user"]["username"].asString() == "rye.resident"); + CHECK(event["tags"]["feedback_kind"].asString() == "idea"); + CHECK(event["tags"]["run_id"].asString() == "run"); + UNSCOPED_INFO("an empty tag is left out"); + REQUIRE_FALSE(event["tags"].has("empty")); const LLSD& feedback = event["contexts"]["feedback"]; - ensure_equals("message", feedback["message"].asString(), "More knobs"); - ensure_equals("email", feedback["contact_email"].asString(), "rye@example.com"); - ensure_equals("name", feedback["name"].asString(), "Rye"); - ensure_equals("associated", feedback["associated_event_id"].asString(), message.associatedEventId); - ensure_equals("os", event["contexts"]["os"]["name"].asString(), context.osName); - ensure_equals("gpu", event["contexts"]["gpu"]["vendor_name"].asString(), "NVIDIA"); + CHECK(feedback["message"].asString() == "More knobs"); + CHECK(feedback["contact_email"].asString() == "rye@example.com"); + CHECK(feedback["name"].asString() == "Rye"); + CHECK(feedback["associated_event_id"].asString() == message.associatedEventId); + CHECK(event["contexts"]["os"]["name"].asString() == context.osName); + CHECK(event["contexts"]["gpu"]["vendor_name"].asString() == "NVIDIA"); } - template<> template<> - void object::test<12>() + TEST_CASE("alfeedback: what the user left empty is not in the event", "[alfeedback]") { - set_test_name("what the user left empty is not in the event"); ALFeedback::Message message; message.text = "It broke"; const LLSD event = ALFeedback::feedbackEvent("feedc0de", "2026-10-09T12:00:00Z", message, ALFeedback::Context()); - ensure("no user", !event.has("user")); - ensure("no release", !event.has("release")); - ensure("no os", !event["contexts"].has("os")); - ensure("no gpu", !event["contexts"].has("gpu")); + REQUIRE_FALSE(event.has("user")); + REQUIRE_FALSE(event.has("release")); + REQUIRE_FALSE(event["contexts"].has("os")); + REQUIRE_FALSE(event["contexts"].has("gpu")); const LLSD& feedback = event["contexts"]["feedback"]; - ensure("no email", !feedback.has("contact_email")); - ensure("no name", !feedback.has("name")); - ensure("no association", !feedback.has("associated_event_id")); - ensure_equals("problem by default", event["tags"]["feedback_kind"].asString(), "problem"); + REQUIRE_FALSE(feedback.has("contact_email")); + REQUIRE_FALSE(feedback.has("name")); + REQUIRE_FALSE(feedback.has("associated_event_id")); + UNSCOPED_INFO("problem by default"); + CHECK(event["tags"]["feedback_kind"].asString() == "problem"); } - template<> template<> - void object::test<13>() + TEST_CASE("alfeedback: a tag value is one line cut on a character within Sentry's limit", "[alfeedback]") { - set_test_name("a tag value is one line within Sentry's limit, cut on a character"); ALFeedback::Context context; std::string long_value(ALFeedback::TAG_VALUE_MAX_BYTES - 1, 'a'); long_value += "\xC3\xA9tail"; context.tags = { { "long", long_value }, { "lines", "one\ntwo\r\n" } }; const LLSD event = ALFeedback::feedbackEvent("id", "t", ALFeedback::Message(), context); - ensure_equals("cut before the character", event["tags"]["long"].asString(), + UNSCOPED_INFO("cut before the character"); + CHECK(event["tags"]["long"].asString() == std::string(ALFeedback::TAG_VALUE_MAX_BYTES - 1, 'a')); - ensure_equals("one line", event["tags"]["lines"].asString(), "one two "); + UNSCOPED_INFO("one line"); + CHECK(event["tags"]["lines"].asString() == "one two "); } - template<> template<> - void object::test<14>() + TEST_CASE("alfeedback: the envelope is a header then the feedback then attachments by byte length", "[alfeedback]") { - set_test_name("the envelope is a header, the feedback, then each attachment, lengths in bytes"); ALFeedback::Message message; message.text = "caf\xC3\xA9 \xF0\x9F\x99\x82\nsecond line"; const LLSD event = ALFeedback::feedbackEvent("feedc0de", "2026-10-09T12:00:00Z", message, ALFeedback::Context()); @@ -269,137 +258,146 @@ namespace tut size_t pos = 0; const LLSD header = json(take(bytes, pos)); - ensure_equals("header id", header["event_id"].asString(), "feedc0de"); - ensure_equals("header dsn", header["dsn"].asString(), "https://k@h/1"); + CHECK(header["event_id"].asString() == "feedc0de"); + CHECK(header["dsn"].asString() == "https://k@h/1"); // A kept envelope goes again as it is, days later maybe: a time it // was sent would be read as the client's clock being wrong. - ensure("no time sent", !header.has("sent_at")); - ensure_equals("header sdk", header["sdk"]["name"].asString(), "alchemy.feedback"); + REQUIRE_FALSE(header.has("sent_at")); + CHECK(header["sdk"]["name"].asString() == "alchemy.feedback"); const LLSD feedback_item = json(take(bytes, pos)); - ensure_equals("feedback type", feedback_item["type"].asString(), "feedback"); + CHECK(feedback_item["type"].asString() == "feedback"); const LLSD payload = json(take(bytes, pos, feedback_item["length"].asInteger())); - ensure_equals("the message round-trips", payload["contexts"]["feedback"]["message"].asString(), message.text); + UNSCOPED_INFO("the message round-trips"); + CHECK(payload["contexts"]["feedback"]["message"].asString() == message.text); const LLSD screenshot_item = json(take(bytes, pos)); - ensure_equals("attachment type", screenshot_item["type"].asString(), "attachment"); - ensure_equals("filename", screenshot_item["filename"].asString(), "screenshot.png"); - ensure_equals("content type", screenshot_item["content_type"].asString(), "image/png"); - ensure_equals("binary bytes", std::string(take(bytes, pos, screenshot_item["length"].asInteger())), binary); + CHECK(screenshot_item["type"].asString() == "attachment"); + CHECK(screenshot_item["filename"].asString() == "screenshot.png"); + CHECK(screenshot_item["content_type"].asString() == "image/png"); + CHECK(std::string(take(bytes, pos, screenshot_item["length"].asInteger())) == binary); const LLSD log_item = json(take(bytes, pos)); - ensure_equals("log bytes", std::string(take(bytes, pos, log_item["length"].asInteger())), "line\n"); - ensure_equals("nothing after", pos, bytes.size()); + CHECK(std::string(take(bytes, pos, log_item["length"].asInteger())) == "line\n"); + UNSCOPED_INFO("nothing after"); + REQUIRE(pos == bytes.size()); } - template<> template<> - void object::test<15>() + TEST_CASE("alfeedback: gzip makes one member that inflates back to the data", "[alfeedback]") { - set_test_name("gzip makes one member that inflates back to the data"); std::string data; for (int i = 0; i < 2000; ++i) { data += "2026-10-09T12:00:00Z INFO: a log line that repeats\n"; } std::string zipped; - ensure("compressed", ALFeedback::gzip(data, zipped)); - ensure("gzip magic", zipped.size() > 2 && zipped[0] == '\x1f' && zipped[1] == '\x8b'); - ensure("smaller", zipped.size() < data.size() / 4); + REQUIRE(ALFeedback::gzip(data, zipped)); + { + INFO("gzip magic"); + REQUIRE(zipped.size() > 2); + CHECK(zipped[0] == '\x1f'); + CHECK(zipped[1] == '\x8b'); + } + UNSCOPED_INFO("smaller"); + REQUIRE(zipped.size() < data.size() / 4); std::string inflated(data.size(), '\0'); z_stream stream = {}; - ensure_equals("inflate init", inflateInit2(&stream, MAX_WBITS + 16), Z_OK); + CHECK(inflateInit2(&stream, MAX_WBITS + 16) == Z_OK); stream.next_in = reinterpret_cast(zipped.data()); stream.avail_in = static_cast(zipped.size()); stream.next_out = reinterpret_cast(inflated.data()); stream.avail_out = static_cast(inflated.size()); - ensure_equals("inflated whole", inflate(&stream, Z_FINISH), Z_STREAM_END); + UNSCOPED_INFO("inflated whole"); + CHECK(inflate(&stream, Z_FINISH) == Z_STREAM_END); inflateEnd(&stream); - ensure("round trip", inflated == data); + UNSCOPED_INFO("round trip"); + CHECK(inflated == data); } - template<> template<> - void object::test<16>() + TEST_CASE("alfeedback: the server's answer reads as an outcome", "[alfeedback]") { - set_test_name("the server's answer reads as an outcome"); using ALFeedback::Outcome; - ensure("200", ALFeedback::classify(200) == Outcome::Sent); - ensure("429", ALFeedback::classify(429) == Outcome::RateLimited); - ensure("413", ALFeedback::classify(413) == Outcome::TooLarge); - ensure("400", ALFeedback::classify(400) == Outcome::Rejected); - ensure("403", ALFeedback::classify(403) == Outcome::Rejected); - ensure("408", ALFeedback::classify(408) == Outcome::Unreachable); - ensure("503", ALFeedback::classify(503) == Outcome::Unreachable); - ensure("no answer", ALFeedback::classify(0) == Outcome::Unreachable); - ensure("retry when limited", ALFeedback::retryable(Outcome::RateLimited)); - ensure("retry when unreachable", ALFeedback::retryable(Outcome::Unreachable)); - ensure("no retry when refused", !ALFeedback::retryable(Outcome::Rejected)); - ensure("no retry when too large", !ALFeedback::retryable(Outcome::TooLarge)); - ensure("no retry when sent", !ALFeedback::retryable(Outcome::Sent)); + CHECK(ALFeedback::classify(200) == Outcome::Sent); + CHECK(ALFeedback::classify(429) == Outcome::RateLimited); + CHECK(ALFeedback::classify(413) == Outcome::TooLarge); + CHECK(ALFeedback::classify(400) == Outcome::Rejected); + CHECK(ALFeedback::classify(403) == Outcome::Rejected); + CHECK(ALFeedback::classify(408) == Outcome::Unreachable); + CHECK(ALFeedback::classify(503) == Outcome::Unreachable); + UNSCOPED_INFO("no answer"); + CHECK(ALFeedback::classify(0) == Outcome::Unreachable); + CHECK(ALFeedback::retryable(Outcome::RateLimited)); + CHECK(ALFeedback::retryable(Outcome::Unreachable)); + CHECK_FALSE(ALFeedback::retryable(Outcome::Rejected)); + CHECK_FALSE(ALFeedback::retryable(Outcome::TooLarge)); + CHECK_FALSE(ALFeedback::retryable(Outcome::Sent)); } - template<> template<> - void object::test<17>() + TEST_CASE("alfeedback: a kept report's wait doubles from 15 minutes to 6 hours or more if asked", "[alfeedback]") { - set_test_name("a kept report waits a quarter hour, doubling to six hours, never less than asked"); const S64 now = 1000000; - ensure_equals("first", ALFeedback::nextAttempt(now, 1, 0.f), now + 15 * 60); - ensure_equals("second", ALFeedback::nextAttempt(now, 2, 0.f), now + 30 * 60); - ensure_equals("third", ALFeedback::nextAttempt(now, 3, 0.f), now + 60 * 60); - ensure_equals("capped", ALFeedback::nextAttempt(now, 20, 0.f), now + 6 * 60 * 60); - ensure_equals("the server's word", ALFeedback::nextAttempt(now, 1, 3600.5f), now + 3601); + CHECK(ALFeedback::nextAttempt(now, 1, 0.f) == now + 15 * 60); + CHECK(ALFeedback::nextAttempt(now, 2, 0.f) == now + 30 * 60); + CHECK(ALFeedback::nextAttempt(now, 3, 0.f) == now + 60 * 60); + UNSCOPED_INFO("capped"); + CHECK(ALFeedback::nextAttempt(now, 20, 0.f) == now + 6 * 60 * 60); + UNSCOPED_INFO("the server's word"); + CHECK(ALFeedback::nextAttempt(now, 1, 3600.5f) == now + 3601); } - template<> template<> - void object::test<18>() + TEST_CASE("alfeedback: a kept report is given up after a week or five tries", "[alfeedback]") { - set_test_name("a kept report is given up after a week or five tries"); ALFeedback::Queued queued; queued.created = 0; queued.attempts = 4; - ensure("young and tried four times", !ALFeedback::expired(queued, ALFeedback::QUEUE_MAX_AGE_SECONDS)); - ensure("over a week", ALFeedback::expired(queued, ALFeedback::QUEUE_MAX_AGE_SECONDS + 1)); + UNSCOPED_INFO("young and tried four times"); + CHECK_FALSE(ALFeedback::expired(queued, ALFeedback::QUEUE_MAX_AGE_SECONDS)); + UNSCOPED_INFO("over a week"); + CHECK(ALFeedback::expired(queued, ALFeedback::QUEUE_MAX_AGE_SECONDS + 1)); queued.attempts = 5; - ensure("five tries", ALFeedback::expired(queued, 0)); + UNSCOPED_INFO("five tries"); + CHECK(ALFeedback::expired(queued, 0)); } - template<> template<> - void object::test<19>() + TEST_CASE("alfeedback: a kept report's record round-trips past 2038 too", "[alfeedback]") { - set_test_name("a kept report's record round-trips, past 2038 too"); ALFeedback::Queued queued; queued.eventId = "0123abcd89abcdef0123456789abcdef"; queued.created = 4102444800; // 2100 queued.attempts = 3; queued.nextAt = 4102448400; const auto back = ALFeedback::queuedFromJson(ALFeedback::queuedToJson(queued)); - ensure("parsed", back.has_value()); - ensure_equals("id", back->eventId, queued.eventId); - ensure_equals("created", back->created, queued.created); - ensure_equals("attempts", back->attempts, queued.attempts); - ensure_equals("next", back->nextAt, queued.nextAt); - ensure("not a record", !ALFeedback::queuedFromJson("{\"nothing\":1}")); - ensure("not json", !ALFeedback::queuedFromJson("garbage")); + REQUIRE(back.has_value()); + CHECK(back->eventId == queued.eventId); + CHECK(back->created == queued.created); + CHECK(back->attempts == queued.attempts); + CHECK(back->nextAt == queued.nextAt); + UNSCOPED_INFO("not a record"); + REQUIRE_FALSE(ALFeedback::queuedFromJson("{\"nothing\":1}")); + UNSCOPED_INFO("not json"); + REQUIRE_FALSE(ALFeedback::queuedFromJson("garbage")); } - template<> template<> - void object::test<20>() + TEST_CASE("alfeedback: a DSN sends in the clear only to this machine", "[alfeedback]") { - set_test_name("a DSN sends in the clear only to this machine"); const auto local = ALFeedback::endpointFromDsn("http://0123abcd@127.0.0.1:8765/1"); - ensure("loopback", local.has_value()); - ensure_equals(local->url, "http://127.0.0.1:8765/api/1/envelope/"); - ensure("localhost", ALFeedback::endpointFromDsn("http://k@localhost/1").has_value()); - ensure("IPv6 loopback", ALFeedback::endpointFromDsn("http://k@[::1]:9000/1").has_value()); - ensure("another host", !ALFeedback::endpointFromDsn("http://k@o42.ingest.sentry.io/4501")); - ensure("a host that starts like loopback", !ALFeedback::endpointFromDsn("http://k@127.0.0.1.example.com/1")); - ensure("loopback as a user name", !ALFeedback::endpointFromDsn("http://k@evil.example.com/127.0.0.1/1")); + UNSCOPED_INFO("loopback"); + REQUIRE(local.has_value()); + CHECK(local->url == "http://127.0.0.1:8765/api/1/envelope/"); + REQUIRE(ALFeedback::endpointFromDsn("http://k@localhost/1").has_value()); + UNSCOPED_INFO("IPv6 loopback"); + REQUIRE(ALFeedback::endpointFromDsn("http://k@[::1]:9000/1").has_value()); + UNSCOPED_INFO("another host"); + REQUIRE_FALSE(ALFeedback::endpointFromDsn("http://k@o42.ingest.sentry.io/4501")); + UNSCOPED_INFO("a host that starts like loopback"); + REQUIRE_FALSE(ALFeedback::endpointFromDsn("http://k@127.0.0.1.example.com/1")); + UNSCOPED_INFO("loopback as a user name"); + REQUIRE_FALSE(ALFeedback::endpointFromDsn("http://k@evil.example.com/127.0.0.1/1")); } - template<> template<> - void object::test<21>() + TEST_CASE("alfeedback: a report's logs hide capability addresses and the given words", "[alfeedback]") { - set_test_name("a report's logs hide capability addresses and the given words"); const std::string log = "INFO: Requesting seed from https://simhost-0a1b.agni.lindenlab.com:12043/cap/" "0f1e2d3c-4b5a-6978-8796-a5b4c3d2e1f0 region name Ahern\n" @@ -408,88 +406,103 @@ namespace tut "INFO: Logging out as agent: a2e76fcd-9360-4f6d-a924-000000000003\n"; const std::string scrubbed = ALFeedback::scrubLog(log, { "rye resident", "rye.resident", "a2e76fcd-9360-4f6d-a924-000000000003", "" }); - ensure("a Second Life capability", scrubbed.find("0f1e2d3c") == std::string::npos); - ensure("an OpenSim capability, any case", scrubbed.find("0F1E2D3C") == std::string::npos); - ensure("what is not secret stays", scrubbed.find("simhost-0a1b.agni.lindenlab.com:12043/cap/[hidden] region name Ahern") + UNSCOPED_INFO("a Second Life capability"); + REQUIRE(scrubbed.find("0f1e2d3c") == std::string::npos); + UNSCOPED_INFO("an OpenSim capability, any case"); + REQUIRE(scrubbed.find("0F1E2D3C") == std::string::npos); + UNSCOPED_INFO("what is not secret stays"); + REQUIRE(scrubbed.find("simhost-0a1b.agni.lindenlab.com:12043/cap/[hidden] region name Ahern") != std::string::npos); - ensure("the path after it stays", scrubbed.find("/CAPS/[hidden]/'") != std::string::npos); - ensure("a name, any case", scrubbed.find("Attempting login as: [hidden]\n") != std::string::npos); - ensure("an id", scrubbed.find("agent: [hidden]\n") != std::string::npos); - ensure_equals("nothing to hide is the log", ALFeedback::scrubLog("plain line\n", {}), "plain line\n"); + UNSCOPED_INFO("the path after it stays"); + REQUIRE(scrubbed.find("/CAPS/[hidden]/'") != std::string::npos); + UNSCOPED_INFO("a name, any case"); + REQUIRE(scrubbed.find("Attempting login as: [hidden]\n") != std::string::npos); + UNSCOPED_INFO("an id"); + REQUIRE(scrubbed.find("agent: [hidden]\n") != std::string::npos); + UNSCOPED_INFO("nothing to hide is the log"); + CHECK(ALFeedback::scrubLog("plain line\n", {}) == "plain line\n"); } - template<> template<> - void object::test<22>() + TEST_CASE("alfeedback: only an id can name a file", "[alfeedback]") { - set_test_name("only an id can name a file"); - ensure("an id", ALFeedback::validEventId(ALFeedback::newEventId())); - ensure("upper case", !ALFeedback::validEventId("0123456789ABCDEF0123456789abcdef")); - ensure("short", !ALFeedback::validEventId("0123456789abcdef")); - ensure("a path", !ALFeedback::validEventId("../../../../../../../../../../x")); - ensure("dashed", !ALFeedback::validEventId("01234567-89ab-cdef-0123-456789abcdef")); - ensure("nil", !ALFeedback::validEventId("00000000000000000000000000000000")); - ensure_equals("a reference is its start", ALFeedback::reference("0123456789abcdef0123456789abcdef"), "01234567"); + CHECK(ALFeedback::validEventId(ALFeedback::newEventId())); + UNSCOPED_INFO("upper case"); + CHECK_FALSE(ALFeedback::validEventId("0123456789ABCDEF0123456789abcdef")); + UNSCOPED_INFO("short"); + CHECK_FALSE(ALFeedback::validEventId("0123456789abcdef")); + UNSCOPED_INFO("a path"); + CHECK_FALSE(ALFeedback::validEventId("../../../../../../../../../../x")); + UNSCOPED_INFO("dashed"); + CHECK_FALSE(ALFeedback::validEventId("01234567-89ab-cdef-0123-456789abcdef")); + UNSCOPED_INFO("nil"); + CHECK_FALSE(ALFeedback::validEventId("00000000000000000000000000000000")); + UNSCOPED_INFO("a reference is its start"); + CHECK(ALFeedback::reference("0123456789abcdef0123456789abcdef") == "01234567"); } - template<> template<> - void object::test<23>() + TEST_CASE("alfeedback: a changed setting's text shows only when it is plain", "[alfeedback]") { - set_test_name("a changed setting's text shows only when it is plain"); - ensure_equals("a number", ALFeedback::settingText("RenderQualityPerformance", LLSD(3)), "i3"); - ensure_equals("a word", ALFeedback::settingText("SkinCurrent", LLSD("alchemy")), "'alchemy'"); - ensure_equals("a path", ALFeedback::settingText("InstantMessageLogPath", LLSD("C:\\Users\\rye\\chat")), + UNSCOPED_INFO("a number"); + CHECK(ALFeedback::settingText("RenderQualityPerformance", LLSD(3)) == "i3"); + UNSCOPED_INFO("a word"); + CHECK(ALFeedback::settingText("SkinCurrent", LLSD("alchemy")) == "'alchemy'"); + UNSCOPED_INFO("a path"); + CHECK(ALFeedback::settingText("InstantMessageLogPath", LLSD("C:\\Users\\rye\\chat")) == "(hidden)"); - ensure_equals("a sentence", ALFeedback::settingText("DoNotDisturbModeResponse", LLSD("Away right now")), + UNSCOPED_INFO("a sentence"); + CHECK(ALFeedback::settingText("DoNotDisturbModeResponse", LLSD("Away right now")) == "(hidden)"); - ensure_equals("an address", ALFeedback::settingText("StreamURL", LLSD("http://radio.example.com/?k=1")), + UNSCOPED_INFO("an address"); + CHECK(ALFeedback::settingText("StreamURL", LLSD("http://radio.example.com/?k=1")) == "(hidden)"); - ensure_equals("a name", ALFeedback::settingText("LastName", LLSD("Resident")), "(hidden)"); - ensure_equals("a list", ALFeedback::settingText("StreamList", LLSD::emptyArray()), "(hidden)"); + UNSCOPED_INFO("a name"); + CHECK(ALFeedback::settingText("LastName", LLSD("Resident")) == "(hidden)"); + UNSCOPED_INFO("a list"); + CHECK(ALFeedback::settingText("StreamList", LLSD::emptyArray()) == "(hidden)"); } - template<> template<> - void object::test<24>() + TEST_CASE("alfeedback: a wait the server asks for is a sane number of seconds", "[alfeedback]") { - set_test_name("a wait the server asks for is a sane number of seconds"); - ensure_equals("none", ALFeedback::serverWait(0.f), 0); - ensure_equals("negative", ALFeedback::serverWait(-5.f), 0); - ensure_equals("not a number", ALFeedback::serverWait(std::numeric_limits::quiet_NaN()), 0); - ensure_equals("rounded up", ALFeedback::serverWait(90.2f), 91); - ensure_equals("forever is a week", ALFeedback::serverWait(std::numeric_limits::infinity()), + CHECK(ALFeedback::serverWait(0.f) == 0); + CHECK(ALFeedback::serverWait(-5.f) == 0); + CHECK(ALFeedback::serverWait(std::numeric_limits::quiet_NaN()) == 0); + UNSCOPED_INFO("rounded up"); + CHECK(ALFeedback::serverWait(90.2f) == 91); + UNSCOPED_INFO("forever is a week"); + CHECK(ALFeedback::serverWait(std::numeric_limits::infinity()) == ALFeedback::QUEUE_MAX_AGE_SECONDS); - ensure_equals("too long is a week", ALFeedback::serverWait(1e30f), ALFeedback::QUEUE_MAX_AGE_SECONDS); + UNSCOPED_INFO("too long is a week"); + CHECK(ALFeedback::serverWait(1e30f) == ALFeedback::QUEUE_MAX_AGE_SECONDS); const S64 now = 1000000; - ensure("never before now", ALFeedback::nextAttempt(now, 1, std::numeric_limits::infinity()) > now); + UNSCOPED_INFO("never before now"); + CHECK(ALFeedback::nextAttempt(now, 1, std::numeric_limits::infinity()) > now); } - template<> template<> - void object::test<25>() + TEST_CASE("alfeedback: a kept record keeps what the user wrote and refuses what is not one", "[alfeedback]") { - set_test_name("a kept record keeps what the user wrote, and refuses what is not one"); ALFeedback::Queued queued; queued.eventId = "0123abcd89abcdef0123456789abcdef"; queued.notBefore = 4102448500; queued.kind = ALFeedback::Kind::Idea; queued.message = "More knobs \xF0\x9F\x99\x82"; const auto back = ALFeedback::queuedFromJson(ALFeedback::queuedToJson(queued)); - ensure("parsed", back.has_value()); - ensure_equals("not before", back->notBefore, queued.notBefore); - ensure("kind", back->kind == ALFeedback::Kind::Idea); - ensure_equals("message", back->message, queued.message); - ensure("a path for an id", - !ALFeedback::queuedFromJson("{\"event_id\":\"../../x\",\"created\":1,\"attempts\":0}")); + REQUIRE(back.has_value()); + CHECK(back->notBefore == queued.notBefore); + CHECK(back->kind == ALFeedback::Kind::Idea); + CHECK(back->message == queued.message); + UNSCOPED_INFO("a path for an id"); + REQUIRE_FALSE(ALFeedback::queuedFromJson("{\"event_id\":\"../../x\",\"created\":1,\"attempts\":0}")); const auto wild = ALFeedback::queuedFromJson( "{\"event_id\":\"0123abcd89abcdef0123456789abcdef\",\"created\":1e300,\"next_at\":-5,\"attempts\":-3}"); - ensure("times that are not times", wild.has_value()); - ensure_equals("created", wild->created, 0); - ensure_equals("next", wild->nextAt, 0); - ensure_equals("attempts", wild->attempts, 0); + UNSCOPED_INFO("times that are not times"); + REQUIRE(wild.has_value()); + CHECK(wild->created == 0); + CHECK(wild->nextAt == 0); + CHECK(wild->attempts == 0); } - template<> template<> - void object::test<26>() + TEST_CASE("alfeedback: a draft round-trips and drops what is not an id or a link", "[alfeedback]") { - set_test_name("a draft round-trips, and what is not an id or a link is dropped"); ALFeedback::Draft draft; draft.kind = ALFeedback::Kind::Other; draft.message = "Half a thought"; @@ -499,31 +512,32 @@ namespace tut draft.linkedAt = "2026-10-09T12:00:00Z"; draft.linkedRunId = "run"; const auto back = ALFeedback::draftFromJson(ALFeedback::draftToJson(draft)); - ensure("parsed", back.has_value()); - ensure("kind", back->kind == ALFeedback::Kind::Other); - ensure_equals("message", back->message, draft.message); - ensure_equals("id", back->eventId, draft.eventId); - ensure_equals("associated", back->associatedEventId, draft.associatedEventId); - ensure_equals("linked", back->linked, "freeze"); - ensure_equals("when", back->linkedAt, draft.linkedAt); - ensure_equals("run", back->linkedRunId, "run"); + REQUIRE(back.has_value()); + CHECK(back->kind == ALFeedback::Kind::Other); + CHECK(back->message == draft.message); + CHECK(back->eventId == draft.eventId); + CHECK(back->associatedEventId == draft.associatedEventId); + CHECK(back->linked == "freeze"); + CHECK(back->linkedAt == draft.linkedAt); + CHECK(back->linkedRunId == "run"); const auto odd = ALFeedback::draftFromJson( "{\"message\":\"hi there\",\"event_id\":\"../x\",\"linked\":\"party\",\"kind\":\"nonsense\"}"); - ensure("parsed", odd.has_value()); - ensure("no id", odd->eventId.empty()); - ensure("no link", odd->linked.empty()); - ensure("a problem", odd->kind == ALFeedback::Kind::Problem); - ensure("an empty draft is none", !ALFeedback::draftFromJson("{\"message\":\" \\n\"}")); + REQUIRE(odd.has_value()); + REQUIRE(odd->eventId.empty()); + REQUIRE(odd->linked.empty()); + UNSCOPED_INFO("a problem"); + CHECK(odd->kind == ALFeedback::Kind::Problem); + UNSCOPED_INFO("an empty draft is none"); + REQUIRE_FALSE(ALFeedback::draftFromJson("{\"message\":\" \\n\"}")); } - template<> template<> - void object::test<27>() + TEST_CASE("alfeedback: kinds and outcomes have names", "[alfeedback]") { - set_test_name("kinds and outcomes have names"); - ensure("idea", ALFeedback::kindFromName(ALFeedback::kindName(ALFeedback::Kind::Idea)) == ALFeedback::Kind::Idea); - ensure("other", ALFeedback::kindFromName("other") == ALFeedback::Kind::Other); - ensure("anything else", ALFeedback::kindFromName("bug") == ALFeedback::Kind::Problem); - ensure_equals(std::string(ALFeedback::outcomeName(ALFeedback::Outcome::RateLimited)), "rate limited"); + CHECK(ALFeedback::kindFromName(ALFeedback::kindName(ALFeedback::Kind::Idea)) == ALFeedback::Kind::Idea); + CHECK(ALFeedback::kindFromName("other") == ALFeedback::Kind::Other); + UNSCOPED_INFO("anything else"); + CHECK(ALFeedback::kindFromName("bug") == ALFeedback::Kind::Problem); + CHECK(std::string(ALFeedback::outcomeName(ALFeedback::Outcome::RateLimited)) == "rate limited"); } } diff --git a/indra/newview/tests/alfeedbackoutbox_test.cpp b/indra/newview/tests/alfeedbackoutbox_test.cpp index 19fce656f5..a17faa7e9e 100644 --- a/indra/newview/tests/alfeedbackoutbox_test.cpp +++ b/indra/newview/tests/alfeedbackoutbox_test.cpp @@ -32,7 +32,7 @@ #include "llfile.h" #include "lluuid.h" -#include "../test/lltut.h" +#include #include @@ -70,7 +70,7 @@ namespace } } -namespace tut +namespace { struct alfeedbackoutbox_data { @@ -93,77 +93,66 @@ namespace tut } }; - typedef test_group alfeedbackoutbox_group; - typedef alfeedbackoutbox_group::object object; - alfeedbackoutbox_group alfeedbackoutboxgrp("alfeedbackoutbox"); - - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(alfeedbackoutbox_data, "alfeedbackoutbox: a kept report is a body and a record due a quarter hour later", "[alfeedbackoutbox]") { - set_test_name("a kept report is a body and a record, due a quarter hour later"); ALFeedbackOutbox box(mDir); std::vector given_up; const S64 now = now_seconds(); - ensure("kept", box.keep(report(id(1)), "body", now, given_up)); - ensure("has", box.has(id(1))); - ensure_equals("body", LLFile::getContents(path(id(1), ".envelope")), "body"); + REQUIRE(box.keep(report(id(1)), "body", now, given_up)); + REQUIRE(box.has(id(1))); + CHECK(LLFile::getContents(path(id(1), ".envelope")) == "body"); const auto records = box.records(now, given_up); - ensure_equals("one", records.size(), 1u); - ensure_equals("made now", records[0].created, now); - ensure_equals("due", records[0].nextAt, now + ALFeedback::QUEUE_RETRY_SECONDS); - ensure_equals("what the user wrote", records[0].message, "It broke"); - ensure("nothing given up", given_up.empty()); + REQUIRE(records.size() == 1u); + CHECK(records[0].created == now); + CHECK(records[0].nextAt == now + ALFeedback::QUEUE_RETRY_SECONDS); + CHECK(records[0].message == "It broke"); + REQUIRE(given_up.empty()); } - template<> template<> - void object::test<2>() + TEST_CASE_METHOD(alfeedbackoutbox_data, "alfeedbackoutbox: an id that would leave the folder names no file", "[alfeedbackoutbox]") { - set_test_name("an id that is not one names no file"); ALFeedbackOutbox box(mDir); std::vector given_up; - ensure("not kept", !box.keep(report("../escape"), "body", now_seconds(), given_up)); - ensure("not claimed", !box.claim("../escape")); - ensure("nothing written", gDirUtilp->getFilesInDir(mDir).empty()); + CHECK_FALSE(box.keep(report("../escape"), "body", now_seconds(), given_up)); + CHECK_FALSE(box.claim("../escape")); + REQUIRE(gDirUtilp->getFilesInDir(mDir).empty()); } - template<> template<> - void object::test<3>() + TEST_CASE_METHOD(alfeedbackoutbox_data, "alfeedbackoutbox: a claimed report cannot be claimed again until released", "[alfeedbackoutbox]") { - set_test_name("a claim is one run's: a second claim finds nothing"); ALFeedbackOutbox box(mDir); ALFeedbackOutbox other(mDir); std::vector given_up; box.keep(report(id(1)), "body", now_seconds(), given_up); - ensure("claimed", box.claim(id(1))); - ensure("not twice", !other.claim(id(1))); - ensure_equals("its body", box.claimedBody(id(1)), "body"); - ensure("still kept", box.has(id(1))); + CHECK(box.claim(id(1))); + CHECK_FALSE(other.claim(id(1))); + CHECK(box.claimedBody(id(1)) == "body"); + UNSCOPED_INFO("a claimed report is still kept"); + REQUIRE(box.has(id(1))); box.release(id(1)); - ensure("free again", other.claim(id(1))); + CHECK(other.claim(id(1))); } - template<> template<> - void object::test<4>() + TEST_CASE_METHOD(alfeedbackoutbox_data, "alfeedbackoutbox: another run leaves a fresh claim on an old report alone", "[alfeedbackoutbox]") { - set_test_name("a claim is fresh however old its report: another run leaves it alone"); ALFeedbackOutbox box(mDir); std::vector given_up; const S64 now = now_seconds(); box.keep(report(id(1)), "body", now, given_up); // A report kept for hours: the body's time is old, the claim's is not. age(path(id(1), ".envelope"), 3 * 60 * 60); - ensure("claimed", box.claim(id(1))); + CHECK(box.claim(id(1))); ALFeedbackOutbox(mDir).records(now, given_up); - ensure("still claimed", LLFile::isfile(path(id(1), ".sending"))); - ensure("not freed", !LLFile::isfile(path(id(1), ".envelope"))); + UNSCOPED_INFO("still claimed"); + CHECK(LLFile::isfile(path(id(1), ".sending"))); + UNSCOPED_INFO("not freed"); + CHECK_FALSE(LLFile::isfile(path(id(1), ".envelope"))); } - template<> template<> - void object::test<5>() + TEST_CASE_METHOD(alfeedbackoutbox_data, "alfeedbackoutbox: a claim left by a run that ended mid-send is free after an hour", "[alfeedbackoutbox]") { - set_test_name("a claim left by a run that ended mid-send is free after an hour"); ALFeedbackOutbox box(mDir); std::vector given_up; const S64 now = now_seconds(); @@ -171,13 +160,12 @@ namespace tut box.claim(id(1)); age(path(id(1), ".sending"), ALFeedbackOutbox::CLAIM_STALE_SECONDS + 60); box.records(now, given_up); - ensure("freed", LLFile::isfile(path(id(1), ".envelope"))); + UNSCOPED_INFO("the stale claim is freed"); + CHECK(LLFile::isfile(path(id(1), ".envelope"))); } - template<> template<> - void object::test<6>() + TEST_CASE_METHOD(alfeedbackoutbox_data, "alfeedbackoutbox: a sent or refused report goes and a rate-limited one waits", "[alfeedbackoutbox]") { - set_test_name("the server's answer settles a report"); ALFeedbackOutbox box(mDir); std::vector given_up; const S64 now = now_seconds(); @@ -185,25 +173,26 @@ namespace tut box.keep(report(id(2)), "body", now, given_up); box.keep(report(id(3)), "body", now, given_up); - ensure("sent is gone", !box.settle(id(1), false, ALFeedback::Outcome::Sent, 0.f, now).kept); - ensure("forgotten", !box.has(id(1))); - ensure("refused is gone", !box.settle(id(2), false, ALFeedback::Outcome::Rejected, 0.f, now).kept); + CHECK_FALSE(box.settle(id(1), false, ALFeedback::Outcome::Sent, 0.f, now).kept); + REQUIRE_FALSE(box.has(id(1))); + CHECK_FALSE(box.settle(id(2), false, ALFeedback::Outcome::Rejected, 0.f, now).kept); box.claim(id(3)); const auto settled = box.settle(id(3), true, ALFeedback::Outcome::RateLimited, 7200.f, now); - ensure("unreached is kept", settled.kept); - ensure("released", LLFile::isfile(path(id(3), ".envelope"))); + UNSCOPED_INFO("a rate-limited report is kept"); + CHECK(settled.kept); + UNSCOPED_INFO("the claim is released"); + CHECK(LLFile::isfile(path(id(3), ".envelope"))); const auto records = box.records(now, given_up); - ensure_equals("one left", records.size(), 1u); - ensure_equals("tried", records[0].attempts, 1); - ensure_equals("waits as asked", records[0].nextAt, now + 7200); - ensure_equals("not before", records[0].notBefore, now + 7200); + REQUIRE(records.size() == 1u); + CHECK(records[0].attempts == 1); + UNSCOPED_INFO("waits as long as the server asked"); + CHECK(records[0].nextAt == now + 7200); + CHECK(records[0].notBefore == now + 7200); } - template<> template<> - void object::test<7>() + TEST_CASE_METHOD(alfeedbackoutbox_data, "alfeedbackoutbox: a report tried too often is given up with what the user wrote", "[alfeedbackoutbox]") { - set_test_name("a report tried too often is given up, with what the user wrote"); ALFeedbackOutbox box(mDir); std::vector given_up; const S64 now = now_seconds(); @@ -213,29 +202,25 @@ namespace tut { settled = box.settle(id(1), false, ALFeedback::Outcome::Unreachable, 0.f, now); } - ensure("given up", settled.givenUp.has_value()); - ensure_equals("its words", settled.givenUp->message, "Lost words"); - ensure("forgotten", !box.has(id(1))); + REQUIRE(settled.givenUp.has_value()); + CHECK(settled.givenUp->message == "Lost words"); + REQUIRE_FALSE(box.has(id(1))); } - template<> template<> - void object::test<8>() + TEST_CASE_METHOD(alfeedbackoutbox_data, "alfeedbackoutbox: a report forgotten while it was sending is not kept again", "[alfeedbackoutbox]") { - set_test_name("a report forgotten while it went is not kept again"); ALFeedbackOutbox box(mDir); std::vector given_up; const S64 now = now_seconds(); box.keep(report(id(1)), "body", now, given_up); box.claim(id(1)); box.forget(id(1)); - ensure("not kept", !box.settle(id(1), true, ALFeedback::Outcome::Unreachable, 0.f, now).kept); - ensure("nothing left", gDirUtilp->getFilesInDir(mDir).empty()); + CHECK_FALSE(box.settle(id(1), true, ALFeedback::Outcome::Unreachable, 0.f, now).kept); + REQUIRE(gDirUtilp->getFilesInDir(mDir).empty()); } - template<> template<> - void object::test<9>() + TEST_CASE_METHOD(alfeedbackoutbox_data, "alfeedbackoutbox: the oldest reports make room and are given back", "[alfeedbackoutbox]") { - set_test_name("the oldest reports make room, and are given back"); ALFeedbackOutbox box(mDir); std::vector given_up; const S64 now = now_seconds(); @@ -243,17 +228,17 @@ namespace tut { box.keep(report(id(i)), "body", now + i, given_up); } - ensure("room so far", given_up.empty()); + UNSCOPED_INFO("nothing given up while there is room"); + REQUIRE(given_up.empty()); box.keep(report(id(99)), "body", now + 99, given_up); - ensure_equals("one made room", given_up.size(), 1u); - ensure_equals("the oldest", given_up[0].eventId, id(0)); - ensure_equals("as many as fit", box.records(now + 100, given_up).size(), ALFeedback::QUEUE_MAX_ENTRIES); + REQUIRE(given_up.size() == 1u); + UNSCOPED_INFO("the oldest is given back"); + CHECK(given_up[0].eventId == id(0)); + REQUIRE(box.records(now + 100, given_up).size() == ALFeedback::QUEUE_MAX_ENTRIES); } - template<> template<> - void object::test<10>() + TEST_CASE_METHOD(alfeedbackoutbox_data, "alfeedbackoutbox: what a run left half-written is tidied away once it is old", "[alfeedbackoutbox]") { - set_test_name("what a run left half-written is tidied away once it is old"); ALFeedbackOutbox box(mDir); std::vector given_up; const S64 now = now_seconds(); @@ -266,24 +251,29 @@ namespace tut ALFeedbackOutbox::replaceFile(path(id(3), ".json"), "not json"); ALFeedbackOutbox::replaceFile(gDirUtilp->add(mDir, "x.json.0123.tmp"), "part"); - ensure("young: kept", gDirUtilp->getFilesInDir(mDir).size() == 4u && box.records(now, given_up).empty()); + { + INFO("young: kept"); + REQUIRE(gDirUtilp->getFilesInDir(mDir).size() == 4u); + REQUIRE(box.records(now, given_up).empty()); + } for (const std::string& name : gDirUtilp->getFilesInDir(mDir)) { age(gDirUtilp->add(mDir, name), ALFeedbackOutbox::ORPHAN_SECONDS + 60); } box.records(now, given_up); - ensure("old: gone", gDirUtilp->getFilesInDir(mDir).empty()); - ensure("nothing was a report", given_up.empty()); + UNSCOPED_INFO("old: gone"); + REQUIRE(gDirUtilp->getFilesInDir(mDir).empty()); + UNSCOPED_INFO("nothing was a report"); + REQUIRE(given_up.empty()); } - template<> template<> - void object::test<11>() + TEST_CASE_METHOD(alfeedbackoutbox_data, "alfeedbackoutbox: replaceFile replaces a file whole and leaves no parts", "[alfeedbackoutbox]") { - set_test_name("a file is replaced whole"); const std::string file = gDirUtilp->add(mDir, "file.json"); - ensure("written", ALFeedbackOutbox::replaceFile(file, "first")); - ensure("replaced", ALFeedbackOutbox::replaceFile(file, "second")); - ensure_equals("the new", LLFile::getContents(file), "second"); - ensure_equals("no parts left", gDirUtilp->getFilesInDir(mDir).size(), 1u); + CHECK(ALFeedbackOutbox::replaceFile(file, "first")); + CHECK(ALFeedbackOutbox::replaceFile(file, "second")); + CHECK(LLFile::getContents(file) == "second"); + UNSCOPED_INFO("no parts left"); + REQUIRE(gDirUtilp->getFilesInDir(mDir).size() == 1u); } } diff --git a/indra/newview/tests/algradehistory_test.cpp b/indra/newview/tests/algradehistory_test.cpp index dd6e84fc20..a903e326fb 100644 --- a/indra/newview/tests/algradehistory_test.cpp +++ b/indra/newview/tests/algradehistory_test.cpp @@ -17,13 +17,13 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../algradehistory.h" #include "llformat.h" -namespace tut +namespace { struct history_data { @@ -39,74 +39,67 @@ namespace tut } }; - typedef test_group history_group; - typedef history_group::object history_object; - tut::history_group hg("ALGradeHistory"); - // The basic contract: one change, one step, and undo hands back the value // it started from. - template<> template<> - void history_object::test<1>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: one change is one step whose undo hands back where it started", "[ALGradeHistory]") { - ensure("nothing to undo yet", !mHistory.canUndo()); - ensure("nothing to redo yet", !mHistory.canRedo()); + CHECK_FALSE(mHistory.canUndo()); + CHECK_FALSE(mHistory.canRedo()); mHistory.record("RenderColorGradeSaturation", LLSD(1.0), LLSD(1.5), 1.f); - ensure("something to undo", mHistory.canUndo()); - ensure_equals("one step", mHistory.depth(), (size_t)1); + CHECK(mHistory.canUndo()); + CHECK(mHistory.depth() == (size_t)1); const auto* t = mHistory.undo(); - ensure("undo returned a transaction", t != nullptr); - ensure_equals("covering one control", t->size(), (size_t)1); - ensure_equals("the right control", t->front().mName, std::string("RenderColorGradeSaturation")); - ensure_equals("restoring the original", t->front().mBefore.asReal(), 1.0); - ensure("nothing left to undo", !mHistory.canUndo()); - ensure("but something to redo", mHistory.canRedo()); + REQUIRE(t != nullptr); + REQUIRE(t->size() == (size_t)1); + CHECK(t->front().mName == std::string("RenderColorGradeSaturation")); + CHECK(t->front().mBefore.asReal() == 1.0); + CHECK_FALSE(mHistory.canUndo()); + CHECK(mHistory.canRedo()); } // The one everybody notices. A wheel drag emits a commit per mouse-move; // without coalescing one drag is a hundred undo steps and Ctrl+Z looks // broken. The whole drag must collapse to a single step -- and that step // must start from where the drag started, not from its penultimate value. - template<> template<> - void history_object::test<2>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: a whole drag coalesces into one step from where it began", "[ALGradeHistory]") { drag("RenderColorGradeLift", 100, 0.01f); - ensure_equals("a whole drag is one step", mHistory.depth(), (size_t)1); + CHECK(mHistory.depth() == (size_t)1); const auto* t = mHistory.undo(); - ensure("got the step", t != nullptr); - ensure_equals("from where the drag began", t->front().mBefore.asReal(), 0.0); - ensure_equals("to where it ended", t->front().mAfter.asReal(), 100.0); + REQUIRE(t != nullptr); + UNSCOPED_INFO("from where the drag began"); + CHECK(t->front().mBefore.asReal() == 0.0); + UNSCOPED_INFO("to where it ended"); + CHECK(t->front().mAfter.asReal() == 100.0); } // Pausing ends the gesture: two deliberate edits are two steps, however // much the same control they touch. - template<> template<> - void history_object::test<3>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: a pause ends the gesture so two edits of one control are two steps", "[ALGradeHistory]") { mHistory.record("RenderColorGradeGain", LLSD(1.0), LLSD(1.1), 1.f); mHistory.record("RenderColorGradeGain", LLSD(1.1), LLSD(1.2), 1.f + ALGradeHistory::COALESCE_SECONDS * 2.f); - ensure_equals("two separate edits", mHistory.depth(), (size_t)2); + CHECK(mHistory.depth() == (size_t)2); } // A different control is always a different action, however fast the user // moved between them -- otherwise nudging saturation would swallow the // contrast change before it. - template<> template<> - void history_object::test<4>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: a different control is a different step however fast it follows", "[ALGradeHistory]") { mHistory.record("RenderColorGradeContrast", LLSD(1.0), LLSD(1.2), 1.f); mHistory.record("RenderColorGradeSaturation", LLSD(1.0), LLSD(1.2), 1.01f); - ensure_equals("two steps", mHistory.depth(), (size_t)2); + CHECK(mHistory.depth() == (size_t)2); } // A section's Reset All writes many controls; that is one thing the user // did, so it must undo in one go. - template<> template<> - void history_object::test<5>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: a group's writes to many controls undo as one step", "[ALGradeHistory]") { mHistory.beginGroup(); mHistory.record("A", LLSD(1.0), LLSD(0.0), 1.f); @@ -114,17 +107,16 @@ namespace tut mHistory.record("C", LLSD(3.0), LLSD(0.0), 1.f); mHistory.endGroup(); - ensure_equals("one step", mHistory.depth(), (size_t)1); + CHECK(mHistory.depth() == (size_t)1); const auto* t = mHistory.undo(); - ensure("got it", t != nullptr); - ensure_equals("covering all three", t->size(), (size_t)3); + REQUIRE(t != nullptr); + REQUIRE(t->size() == (size_t)3); } // Within a group a control may be written more than once. The group should // still describe one before and one after for it, or undo would restore an // intermediate value. - template<> template<> - void history_object::test<6>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: a control written twice in a group keeps one before and one after", "[ALGradeHistory]") { mHistory.beginGroup(); mHistory.record("A", LLSD(1.0), LLSD(2.0), 1.f); @@ -132,91 +124,88 @@ namespace tut mHistory.endGroup(); const auto* t = mHistory.undo(); - ensure("got it", t != nullptr); - ensure_equals("still one control", t->size(), (size_t)1); - ensure_equals("from the original value", t->front().mBefore.asReal(), 1.0); - ensure_equals("to the final one", t->front().mAfter.asReal(), 3.0); + REQUIRE(t != nullptr); + REQUIRE(t->size() == (size_t)1); + CHECK(t->front().mBefore.asReal() == 1.0); + CHECK(t->front().mAfter.asReal() == 3.0); } // An edit made after undoing discards the redo tail: that future is no // longer reachable. - template<> template<> - void history_object::test<7>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: an edit after an undo discards the redo tail", "[ALGradeHistory]") { mHistory.record("A", LLSD(0.0), LLSD(1.0), 1.f); mHistory.record("B", LLSD(0.0), LLSD(1.0), 2.f); mHistory.undo(); - ensure("can redo before the new edit", mHistory.canRedo()); + CHECK(mHistory.canRedo()); mHistory.record("C", LLSD(0.0), LLSD(1.0), 3.f); - ensure("redo tail is gone", !mHistory.canRedo()); - ensure_equals("and the stack was truncated", mHistory.depth(), (size_t)2); + CHECK_FALSE(mHistory.canRedo()); + UNSCOPED_INFO("and the stack was truncated"); + CHECK(mHistory.depth() == (size_t)2); } // Redo walks forward again and hands back the destination values. - template<> template<> - void history_object::test<8>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: redo walks forward and hands back the destination values", "[ALGradeHistory]") { mHistory.record("A", LLSD(0.0), LLSD(1.0), 1.f); mHistory.undo(); const auto* t = mHistory.redo(); - ensure("redo returned the step", t != nullptr); - ensure_equals("with the destination", t->front().mAfter.asReal(), 1.0); - ensure("nothing further to redo", !mHistory.canRedo()); - ensure("and it is undoable again", mHistory.canUndo()); + REQUIRE(t != nullptr); + CHECK(t->front().mAfter.asReal() == 1.0); + CHECK_FALSE(mHistory.canRedo()); + CHECK(mHistory.canUndo()); } // Applying an undo writes settings, which is what feeds this class. If that // fed straight back in it would either loop or corrupt the step being // undone, so a write immediately after an undo must start a new step rather // than coalesce into the old one. - template<> template<> - void history_object::test<9>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: a write right after an undo starts a new step", "[ALGradeHistory]") { mHistory.record("A", LLSD(0.0), LLSD(1.0), 1.f); mHistory.undo(); mHistory.record("A", LLSD(1.0), LLSD(2.0), 1.01f); - ensure_equals("the undone step was replaced, not extended", mHistory.depth(), (size_t)1); + UNSCOPED_INFO("the undone step was replaced, not extended"); + CHECK(mHistory.depth() == (size_t)1); const auto* t = mHistory.undo(); - ensure("got the new step", t != nullptr); - ensure_equals("carrying the new values", t->front().mBefore.asReal(), 1.0); + REQUIRE(t != nullptr); + UNSCOPED_INFO("carrying the new values"); + CHECK(t->front().mBefore.asReal() == 1.0); } // A long session must not grow without bound, and it is the oldest history // that stops being interesting. - template<> template<> - void history_object::test<10>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: the stack is capped at MAX_DEPTH by dropping the oldest", "[ALGradeHistory]") { for (size_t i = 0; i < ALGradeHistory::MAX_DEPTH + 20; ++i) { mHistory.record("A", LLSD((F64)i), LLSD((F64)(i + 1)), (F32)i * 10.f); } - ensure_equals("capped", mHistory.depth(), ALGradeHistory::MAX_DEPTH); + CHECK(mHistory.depth() == ALGradeHistory::MAX_DEPTH); // The newest is still the newest after the drop. const auto* t = mHistory.undo(); - ensure("got the newest", t != nullptr); - ensure_equals("which is the last one recorded", - t->front().mAfter.asReal(), (F64)(ALGradeHistory::MAX_DEPTH + 20)); + REQUIRE(t != nullptr); + UNSCOPED_INFO("which is the last one recorded"); + CHECK(t->front().mAfter.asReal() == (F64)(ALGradeHistory::MAX_DEPTH + 20)); } // An empty group leaves nothing behind -- a Reset All on a section that was // already at defaults should not put a do-nothing step on the stack. - template<> template<> - void history_object::test<11>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: an empty group leaves no step", "[ALGradeHistory]") { mHistory.beginGroup(); mHistory.endGroup(); - ensure("no step", !mHistory.canUndo()); - ensure_equals("nothing recorded", mHistory.depth(), (size_t)0); + CHECK_FALSE(mHistory.canUndo()); + CHECK(mHistory.depth() == (size_t)0); } // Nested groups are still one step: applying a Look may reset sections on // the way through, and the user did one thing. - template<> template<> - void history_object::test<12>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: nested groups are still one step", "[ALGradeHistory]") { mHistory.beginGroup(); mHistory.record("A", LLSD(0.0), LLSD(1.0), 1.f); @@ -226,37 +215,43 @@ namespace tut mHistory.record("C", LLSD(0.0), LLSD(1.0), 1.f); mHistory.endGroup(); - ensure_equals("one step", mHistory.depth(), (size_t)1); + CHECK(mHistory.depth() == (size_t)1); const auto* t = mHistory.undo(); - ensure("got it", t != nullptr); - ensure_equals("covering all three", t->size(), (size_t)3); + REQUIRE(t != nullptr); + REQUIRE(t->size() == (size_t)3); } // Undoing past the beginning, or redoing past the end, answers null rather // than misbehaving -- the key handler calls these without checking first. - template<> template<> - void history_object::test<13>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: undo and redo past either end answer null", "[ALGradeHistory]") { - ensure("undo on empty is null", mHistory.undo() == nullptr); - ensure("redo on empty is null", mHistory.redo() == nullptr); + REQUIRE(mHistory.undo() == nullptr); + REQUIRE(mHistory.redo() == nullptr); mHistory.record("A", LLSD(0.0), LLSD(1.0), 1.f); - ensure("undo works once", mHistory.undo() != nullptr); - ensure("and then stops", mHistory.undo() == nullptr); - ensure("redo works once", mHistory.redo() != nullptr); - ensure("and then stops", mHistory.redo() == nullptr); + UNSCOPED_INFO("undo works once"); + REQUIRE(mHistory.undo() != nullptr); + UNSCOPED_INFO("and then stops"); + REQUIRE(mHistory.undo() == nullptr); + UNSCOPED_INFO("redo works once"); + REQUIRE(mHistory.redo() != nullptr); + UNSCOPED_INFO("and then stops"); + REQUIRE(mHistory.redo() == nullptr); mHistory.clear(); - ensure("clear empties it", !mHistory.canUndo() && !mHistory.canRedo()); - ensure_equals("really empty", mHistory.depth(), (size_t)0); + { + INFO("clear empties it"); + CHECK_FALSE(mHistory.canUndo()); + CHECK_FALSE(mHistory.canRedo()); + } + CHECK(mHistory.depth() == (size_t)0); } // The cap holds for grouped steps too. Groups cannot evict as they push -- // that would shift the index the group is accumulating into -- so the // eviction happens when the group closes, and a session of nothing but // Look applies and section resets must stay bounded like any other. - template<> template<> - void history_object::test<14>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: the cap holds for grouped steps too", "[ALGradeHistory]") { for (size_t i = 0; i < ALGradeHistory::MAX_DEPTH + 20; ++i) { @@ -265,15 +260,16 @@ namespace tut mHistory.record("B", LLSD((F64)i), LLSD((F64)(i + 1)), 1.f); mHistory.endGroup(); } - ensure_equals("capped", mHistory.depth(), ALGradeHistory::MAX_DEPTH); - ensure_equals("everything on the stack is applied", mHistory.cursor(), mHistory.depth()); + CHECK(mHistory.depth() == ALGradeHistory::MAX_DEPTH); + UNSCOPED_INFO("everything on the stack is applied"); + CHECK(mHistory.cursor() == mHistory.depth()); // The newest is still the newest after the drop, and still whole. const auto* t = mHistory.undo(); - ensure("got the newest", t != nullptr); - ensure_equals("still covering both controls", t->size(), (size_t)2); - ensure_equals("which is the last group recorded", - t->front().mAfter.asReal(), (F64)(ALGradeHistory::MAX_DEPTH + 20)); + REQUIRE(t != nullptr); + REQUIRE(t->size() == (size_t)2); + UNSCOPED_INFO("which is the last group recorded"); + CHECK(t->front().mAfter.asReal() == (F64)(ALGradeHistory::MAX_DEPTH + 20)); // And the walk back stops exactly at the cap, with no phantom steps // left over from the evictions. @@ -282,14 +278,14 @@ namespace tut { ++undone; } - ensure_equals("the whole stack is walkable", undone, ALGradeHistory::MAX_DEPTH); + UNSCOPED_INFO("the whole stack is walkable"); + CHECK(undone == ALGradeHistory::MAX_DEPTH); } // A group names the step it makes, for a history that is shown; a step // made any other way has no name, and the caller names it from its // changes. - template<> template<> - void history_object::test<15>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: a group names its step and a lone write's step has no name", "[ALGradeHistory]") { mHistory.beginGroup("Reset Bloom"); mHistory.record("A", LLSD(1.0), LLSD(0.0), 1.f); @@ -297,21 +293,23 @@ namespace tut mHistory.endGroup(); mHistory.record("C", LLSD(0.0), LLSD(1.0), 5.f); - ensure_equals("two steps", mHistory.depth(), (size_t)2); - ensure_equals("the group's step carries its name", mHistory.labelOf(0), std::string("Reset Bloom")); - ensure("a lone write's step has none", mHistory.labelOf(1).empty()); + REQUIRE(mHistory.depth() == (size_t)2); + UNSCOPED_INFO("the group's step carries its name"); + CHECK(mHistory.labelOf(0) == std::string("Reset Bloom")); + UNSCOPED_INFO("a lone write's step has none"); + REQUIRE(mHistory.labelOf(1).empty()); // And a group that follows an unnamed one is not named after it. mHistory.beginGroup(); mHistory.record("D", LLSD(0.0), LLSD(1.0), 9.f); mHistory.endGroup(); - ensure("an unnamed group stays unnamed", mHistory.labelOf(2).empty()); + UNSCOPED_INFO("an unnamed group stays unnamed"); + REQUIRE(mHistory.labelOf(2).empty()); } // Groups nest, and the one the user started is the one the step is named // for: applying a Look that resets a section on the way is "Look: X". - template<> template<> - void history_object::test<16>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: nested groups name their step for the outermost", "[ALGradeHistory]") { mHistory.beginGroup("Look: Soft Film"); mHistory.record("A", LLSD(0.0), LLSD(1.0), 1.f); @@ -320,14 +318,13 @@ namespace tut mHistory.endGroup(); mHistory.endGroup(); - ensure_equals("one step", mHistory.depth(), (size_t)1); - ensure_equals("named by the outermost group", mHistory.labelOf(0), std::string("Look: Soft Film")); + REQUIRE(mHistory.depth() == (size_t)1); + CHECK(mHistory.labelOf(0) == std::string("Look: Soft Film")); } // A name belongs to its step through everything that moves steps: the // oldest dropping off the front at the cap, and the redo tail being cut. - template<> template<> - void history_object::test<17>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: a name stays with its step through eviction and a cut redo tail", "[ALGradeHistory]") { const size_t made = ALGradeHistory::MAX_DEPTH + 5; for (size_t i = 0; i < made; ++i) @@ -336,9 +333,11 @@ namespace tut mHistory.record("A", LLSD((F64)i), LLSD((F64)(i + 1)), 1.f); mHistory.endGroup(); } - ensure_equals("capped", mHistory.depth(), ALGradeHistory::MAX_DEPTH); - ensure_equals("the oldest kept is the sixth made", mHistory.labelOf(0), std::string("step 5")); - ensure_equals("the newest is the last made", mHistory.labelOf(mHistory.depth() - 1), + REQUIRE(mHistory.depth() == ALGradeHistory::MAX_DEPTH); + UNSCOPED_INFO("the oldest kept is the sixth made"); + CHECK(mHistory.labelOf(0) == std::string("step 5")); + UNSCOPED_INFO("the newest is the last made"); + CHECK(mHistory.labelOf(mHistory.depth() - 1) == llformat("step %d", (S32)(made - 1))); mHistory.clear(); @@ -353,51 +352,55 @@ namespace tut mHistory.beginGroup("four"); mHistory.record("four", LLSD(0.0), LLSD(1.0), 1.f); mHistory.endGroup(); - ensure_equals("the undone two were cut", mHistory.depth(), (size_t)2); - ensure_equals("the first kept its name", mHistory.labelOf(0), std::string("one")); - ensure_equals("and the new one has its own", mHistory.labelOf(1), std::string("four")); + UNSCOPED_INFO("the undone two were cut"); + REQUIRE(mHistory.depth() == (size_t)2); + UNSCOPED_INFO("the first kept its name"); + CHECK(mHistory.labelOf(0) == std::string("one")); + UNSCOPED_INFO("and the new one has its own"); + CHECK(mHistory.labelOf(1) == std::string("four")); } // A control a group moved and then put back is not part of what the // group did, and a group made of nothing else is not a step at all. - template<> template<> - void history_object::test<18>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: a control a group moved and put back is not part of its step", "[ALGradeHistory]") { mHistory.beginGroup("There and back"); mHistory.record("A", LLSD(1.0), LLSD(2.0), 1.f); mHistory.record("A", LLSD(2.0), LLSD(1.0), 1.f); mHistory.endGroup(); - ensure("no step", !mHistory.canUndo()); - ensure_equals("nothing kept", mHistory.depth(), (size_t)0); + CHECK_FALSE(mHistory.canUndo()); + CHECK(mHistory.depth() == (size_t)0); mHistory.beginGroup(); mHistory.record("A", LLSD(1.0), LLSD(2.0), 1.f); mHistory.record("B", LLSD(0.0), LLSD(5.0), 1.f); mHistory.record("A", LLSD(2.0), LLSD(1.0), 1.f); mHistory.endGroup(); - ensure_equals("one step", mHistory.depth(), (size_t)1); + CHECK(mHistory.depth() == (size_t)1); const auto* t = mHistory.undo(); - ensure("got it", t != nullptr); - ensure_equals("holding only what changed", t->size(), (size_t)1); - ensure_equals("which is B", t->front().mName, std::string("B")); + REQUIRE(t != nullptr); + UNSCOPED_INFO("holding only what changed"); + REQUIRE(t->size() == (size_t)1); + CHECK(t->front().mName == std::string("B")); } // A drag that comes back to where it started did nothing, so it leaves no // step -- and if it carries on, it is a new step from that same value. - template<> template<> - void history_object::test<19>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: a drag back to where it started leaves no step", "[ALGradeHistory]") { mHistory.record("A", LLSD(0.0), LLSD(1.0), 1.f); mHistory.record("A", LLSD(1.0), LLSD(0.0), 1.1f); - ensure_equals("back where it started, no step", mHistory.depth(), (size_t)0); - ensure("nothing to undo", !mHistory.canUndo()); + CHECK(mHistory.depth() == (size_t)0); + CHECK_FALSE(mHistory.canUndo()); mHistory.record("A", LLSD(0.0), LLSD(2.0), 1.2f); - ensure_equals("carrying on is a step", mHistory.depth(), (size_t)1); + UNSCOPED_INFO("carrying on is a step"); + CHECK(mHistory.depth() == (size_t)1); const auto* t = mHistory.undo(); - ensure("got it", t != nullptr); - ensure_equals("from the value the drag began at", t->front().mBefore.asReal(), 0.0); - ensure_equals("to where it went", t->front().mAfter.asReal(), 2.0); + REQUIRE(t != nullptr); + UNSCOPED_INFO("from the value the drag began at"); + CHECK(t->front().mBefore.asReal() == 0.0); + CHECK(t->front().mAfter.asReal() == 2.0); // A drag that returns to its start does not take an earlier step with // it: only its own is dropped. @@ -405,80 +408,89 @@ namespace tut mHistory.record("B", LLSD(0.0), LLSD(3.0), 1.f); mHistory.record("A", LLSD(0.0), LLSD(1.0), 5.f); mHistory.record("A", LLSD(1.0), LLSD(0.0), 5.1f); - ensure_equals("the earlier step stays", mHistory.depth(), (size_t)1); - ensure_equals("and it is B's", mHistory.at(0).front().mName, std::string("B")); + UNSCOPED_INFO("the earlier step stays"); + REQUIRE(mHistory.depth() == (size_t)1); + CHECK(mHistory.at(0).front().mName == std::string("B")); } // Writing the value already there is not a change, so it must not cost the // user their redo tail. - template<> template<> - void history_object::test<20>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: writing the value already there keeps the redo tail", "[ALGradeHistory]") { mHistory.record("A", LLSD(0.0), LLSD(1.0), 1.f); mHistory.record("B", LLSD(0.0), LLSD(1.0), 2.f); mHistory.undo(); - ensure("something to redo", mHistory.canRedo()); + CHECK(mHistory.canRedo()); mHistory.record("C", LLSD(5.0), LLSD(5.0), 3.f); - ensure("still something to redo", mHistory.canRedo()); - ensure_equals("and nothing added", mHistory.depth(), (size_t)2); + CHECK(mHistory.canRedo()); + UNSCOPED_INFO("and nothing added"); + CHECK(mHistory.depth() == (size_t)2); } // The revision moves with the stack and the cursor and at no other time, // which is what lets a view of the history ask one number whether it is // out of date. - template<> template<> - void history_object::test<21>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: the revision moves with the stack and the cursor and at no other time", "[ALGradeHistory]") { const U32 start = mHistory.revision(); - ensure("reading an empty stack moves nothing", !mHistory.canUndo() && mHistory.revision() == start); + { + INFO("reading an empty stack moves nothing"); + CHECK_FALSE(mHistory.canUndo()); + CHECK(mHistory.revision() == start); + } mHistory.record("A", LLSD(0.0), LLSD(1.0), 1.f); const U32 recorded = mHistory.revision(); - ensure("a write moves it", recorded != start); + UNSCOPED_INFO("a write moves it"); + CHECK(recorded != start); mHistory.depth(); mHistory.cursor(); mHistory.at(0); mHistory.labelOf(0); - ensure_equals("reads do not", mHistory.revision(), recorded); + UNSCOPED_INFO("reads do not"); + CHECK(mHistory.revision() == recorded); mHistory.record("A", LLSD(1.0), LLSD(1.0), 1.1f); - ensure_equals("nor does a write of the value already there", mHistory.revision(), recorded); + UNSCOPED_INFO("nor does a write of the value already there"); + CHECK(mHistory.revision() == recorded); mHistory.undo(); const U32 undone = mHistory.revision(); - ensure("an undo moves it", undone != recorded); + UNSCOPED_INFO("an undo moves it"); + CHECK(undone != recorded); mHistory.redo(); - ensure("a redo moves it", mHistory.revision() != undone); + UNSCOPED_INFO("a redo moves it"); + CHECK(mHistory.revision() != undone); const U32 before_clear = mHistory.revision(); mHistory.clear(); - ensure("a clear moves it", mHistory.revision() != before_clear); + UNSCOPED_INFO("a clear moves it"); + CHECK(mHistory.revision() != before_clear); } // What the stack shows is what undo will do. - template<> template<> - void history_object::test<22>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: what the stack shows is what undo will do", "[ALGradeHistory]") { mHistory.record("A", LLSD(0.0), LLSD(1.0), 1.f); mHistory.record("B", LLSD(2.0), LLSD(3.0), 5.f); const ALGradeHistory::Transaction shown = mHistory.at(1); const auto* undone = mHistory.undo(); - ensure("got it", undone != nullptr); - ensure_equals("the same size", undone->size(), shown.size()); - ensure_equals("the same control", undone->front().mName, shown.front().mName); - ensure_equals("the same start", undone->front().mBefore.asReal(), shown.front().mBefore.asReal()); - ensure_equals("the same end", undone->front().mAfter.asReal(), shown.front().mAfter.asReal()); - ensure_equals("and the cursor says it is no longer in force", mHistory.cursor(), (size_t)1); + REQUIRE(undone != nullptr); + REQUIRE(undone->size() == shown.size()); + CHECK(undone->front().mName == shown.front().mName); + CHECK(undone->front().mBefore.asReal() == shown.front().mBefore.asReal()); + CHECK(undone->front().mAfter.asReal() == shown.front().mAfter.asReal()); + UNSCOPED_INFO("and the cursor says it is no longer in force"); + CHECK(mHistory.cursor() == (size_t)1); } // The stack shown oldest first through its steps forward too, however // many; and a group closing is a change to what it shows, since it // trims its step, where one closing inside it is not. - template<> template<> - void history_object::test<23>() + TEST_CASE_METHOD(history_data, "ALGradeHistory: the stack lists redo steps and only the outermost close is a change", "[ALGradeHistory]") { for (const char* name : { "one", "two", "three" }) { @@ -488,10 +500,14 @@ namespace tut } mHistory.undo(); mHistory.undo(); - ensure_equals("in force", mHistory.labelOf(0), std::string("one")); - ensure_equals("the next redo", mHistory.labelOf(1), std::string("two")); - ensure_equals("the last redo", mHistory.labelOf(2), std::string("three")); - ensure_equals("its changes", mHistory.at(2).front().mName, std::string("three")); + UNSCOPED_INFO("in force"); + CHECK(mHistory.labelOf(0) == std::string("one")); + UNSCOPED_INFO("the next redo"); + CHECK(mHistory.labelOf(1) == std::string("two")); + UNSCOPED_INFO("the last redo"); + CHECK(mHistory.labelOf(2) == std::string("three")); + UNSCOPED_INFO("a redo step's changes"); + CHECK(mHistory.at(2).front().mName == std::string("three")); mHistory.clear(); mHistory.beginGroup("outer"); @@ -499,11 +515,14 @@ namespace tut mHistory.record("A", LLSD(0.0), LLSD(1.0), 1.f); const U32 recorded = mHistory.revision(); mHistory.endGroup(); - ensure_equals("an inner group closing is not a change", mHistory.revision(), recorded); + UNSCOPED_INFO("an inner group closing is not a change"); + CHECK(mHistory.revision() == recorded); mHistory.endGroup(); - ensure("the outermost closing is", mHistory.revision() != recorded); + UNSCOPED_INFO("the outermost closing is"); + CHECK(mHistory.revision() != recorded); const U32 closed = mHistory.revision(); mHistory.endGroup(); - ensure_equals("nor is an end with no group open", mHistory.revision(), closed); + UNSCOPED_INFO("nor is an end with no group open"); + CHECK(mHistory.revision() == closed); } } diff --git a/indra/newview/tests/alhuddepth_gl_test.cpp b/indra/newview/tests/alhuddepth_gl_test.cpp index 502aa365a8..63f453baa7 100644 --- a/indra/newview/tests/alhuddepth_gl_test.cpp +++ b/indra/newview/tests/alhuddepth_gl_test.cpp @@ -39,7 +39,7 @@ #include "../../llrender/tests/llheadlessgl_fixture.h" -#include "../test/lltut.h" +#include #include #include @@ -47,10 +47,8 @@ #include #include -namespace tut +namespace { - namespace - { constexpr S32 W = 64; constexpr S32 H = 64; constexpr F32 FOV = 1.f; @@ -148,11 +146,6 @@ namespace tut return reverse; } - static std::string named(bool reverse, const std::string& what) - { - return what + (reverse ? " (reverse-Z)" : " (forward)"); - } - void setConvention(bool reverse) { LLRender::sReverseZ = reverse; @@ -177,11 +170,12 @@ namespace tut std::map defines; for (const char* object : { "deferred/textureUtilV.glsl", "objects/nonindexedTextureV.glsl" }) { + CAPTURE(object); S32 level = 1; - ensure(std::string("compiled ") + object, mShaders.loadShaderFile(object, level, GL_VERTEX_SHADER, &defines) != 0); + REQUIRE(mShaders.loadShaderFile(object, level, GL_VERTEX_SHADER, &defines) != 0); } S32 level = 1; - ensure("compiled deferred/globalF.glsl", mShaders.loadShaderFile("deferred/globalF.glsl", level, GL_FRAGMENT_SHADER, &defines) != 0); + REQUIRE(mShaders.loadShaderFile("deferred/globalF.glsl", level, GL_FRAGMENT_SHADER, &defines) != 0); mBlitProgram.mName = "Copy Depth Blit"; mBlitProgram.mShaderFiles.clear(); @@ -190,7 +184,7 @@ namespace tut mBlitProgram.clearPermutations(); mBlitProgram.addPermutation("COPY_DEPTH", "1"); mBlitProgram.mShaderLevel = 1; - ensure("blit copy builds", mBlitProgram.createShader()); + REQUIRE(mBlitProgram.createShader()); mCopyDepthProgram.mName = "Copy Depth Shader"; mCopyDepthProgram.mShaderFiles.clear(); @@ -200,11 +194,15 @@ namespace tut mCopyDepthProgram.addPermutation("COPY_DEPTH", "1"); mCopyDepthProgram.addPermutation("DEPTH_ONLY", "1"); mCopyDepthProgram.mShaderLevel = 1; - ensure("depth copy builds", mCopyDepthProgram.createShader()); + REQUIRE(mCopyDepthProgram.createShader()); const GLuint vs = ll_test::compileTestShader(GL_VERTEX_SHADER, kQuadVertex); const GLuint fs = ll_test::compileTestShader(GL_FRAGMENT_SHADER, kQuadFragment); - ensure("quad shaders compile", vs && fs); + { + INFO("quad shaders compile"); + REQUIRE(vs); + REQUIRE(fs); + } mQuadProgram = glCreateProgram(); glAttachShader(mQuadProgram, vs); glAttachShader(mQuadProgram, fs); @@ -213,17 +211,18 @@ namespace tut glDeleteShader(fs); GLint linked = GL_FALSE; glGetProgramiv(mQuadProgram, GL_LINK_STATUS, &linked); - ensure("quad program links", linked == GL_TRUE); + UNSCOPED_INFO("quad program links"); + REQUIRE(linked == GL_TRUE); glGenVertexArrays(1, &mVAO); glGenBuffers(1, &mVBO); - ensure("window", mWindow.allocate(W, H, GL_RGBA8, true, false, ALTextureSlot::TT_TEXTURE, LLRenderTarget::MIPS_NONE, + REQUIRE(mWindow.allocate(W, H, GL_RGBA8, true, false, ALTextureSlot::TT_TEXTURE, LLRenderTarget::MIPS_NONE, LLRenderTarget::DEPTH_FMT_24)); - ensure("scene", mScene.allocate(W, H, GL_RGBA8, true, false, ALTextureSlot::TT_TEXTURE, LLRenderTarget::MIPS_NONE, + REQUIRE(mScene.allocate(W, H, GL_RGBA8, true, false, ALTextureSlot::TT_TEXTURE, LLRenderTarget::MIPS_NONE, reverse ? LLRenderTarget::DEPTH_FMT_32F : LLRenderTarget::DEPTH_FMT_24)); mTriangle = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX); - ensure("triangle", mTriangle->allocateBuffer(3, 0)); + REQUIRE(mTriangle->allocateBuffer(3, 0)); LLStrider vert; mTriangle->getVertexStrider(vert); vert[0].set(-1.f, 1.f, 0.f); @@ -400,17 +399,13 @@ namespace tut bool mReverse = false; }; - typedef test_group alhuddepth_t; - typedef alhuddepth_t::object alhuddepth_object_t; - tut::alhuddepth_t alhuddepth_testcase("ALHUDDepth"); - } // The final blit's copy fills the window's depth with the scene's, as the window's 24 bits hold it. - template<> template<> - void alhuddepth_object_t::test<1>() + TEST_CASE_METHOD(alhuddepth_data, "ALHUDDepth: the final blit fills the window's depth with the scene's", "[ALHUDDepth]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); blit(); const std::vector scene = depths(mScene); @@ -420,43 +415,50 @@ namespace tut { worst = llmax(worst, std::fabs(scene[i] - window[i])); } - ensure(named(reverse, "the window holds the scene's depth"), worst < 1e-6f); - ensure(named(reverse, "and its colour"), colourAt(40, 24) == unorm(MID_COLOUR)); + UNSCOPED_INFO("the window holds the scene's depth"); + REQUIRE(worst < 1e-6f); + UNSCOPED_INFO("and its colour"); + REQUIRE(colourAt(40, 24) == unorm(MID_COLOUR)); const F32 cleared = reverse ? 0.f : 1.f; - ensure_equals(named(reverse, "sky is the cleared depth"), window[(size_t)40 * W + 40], cleared); - ensure(named(reverse, "the near surface is nearer than the mid one"), - reverse ? window[(size_t)24 * W + 24] > window[(size_t)24 * W + 40] : window[(size_t)24 * W + 24] < window[(size_t)24 * W + 40]); + UNSCOPED_INFO("sky is the cleared depth"); + REQUIRE(window[(size_t)40 * W + 40] == cleared); + UNSCOPED_INFO("the near surface is nearer than the mid one"); + REQUIRE((reverse ? window[(size_t)24 * W + 24] > window[(size_t)24 * W + 40] : window[(size_t)24 * W + 24] < window[(size_t)24 * W + 40])); } } // The reported clipping: against the scene's depth the HUD loses to a world surface at the near plane. With the // depth under it cleared, it draws over it. - template<> template<> - void alhuddepth_object_t::test<2>() + TEST_CASE_METHOD(alhuddepth_data, "ALHUDDepth: with the depth under it cleared the HUD draws over the near world", "[ALHUDDepth]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); blit(); drawHUD(); - ensure(named(reverse, "uncleared, the near world hides the HUD"), colourAt(24, 24) == unorm(NEAR_COLOUR)); - ensure(named(reverse, "where there is sky, the HUD shows anyway"), colourAt(24, 40) == unorm(HUD_COLOUR)); + UNSCOPED_INFO("uncleared, the near world hides the HUD"); + REQUIRE(colourAt(24, 24) == unorm(NEAR_COLOUR)); + UNSCOPED_INFO("where there is sky, the HUD shows anyway"); + REQUIRE(colourAt(24, 40) == unorm(HUD_COLOUR)); blit(); clearUnderHUD(); drawHUD(); - ensure(named(reverse, "cleared, the HUD shows over the near world"), colourAt(24, 24) == unorm(HUD_COLOUR)); - ensure(named(reverse, "and over the sky"), colourAt(24, 40) == unorm(HUD_COLOUR)); + UNSCOPED_INFO("cleared, the HUD shows over the near world"); + REQUIRE(colourAt(24, 24) == unorm(HUD_COLOUR)); + UNSCOPED_INFO("and over the sky"); + REQUIRE(colourAt(24, 40) == unorm(HUD_COLOUR)); } } // After the scene's depth is written back, the 3D UI under the HUD's rect is hidden by the world as before, and // by the HUD where the HUD is in front. Without it, what the world hides shows through. - template<> template<> - void alhuddepth_object_t::test<3>() + TEST_CASE_METHOD(alhuddepth_data, "ALHUDDepth: the restored depth hides 3D UI behind the world under the HUD", "[ALHUDDepth]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); blit(); clearUnderHUD(); @@ -466,30 +468,40 @@ namespace tut overlay(44, 20, 3.f); overlay(24, 40, 3.f); overlay(40, 40, 3.f); - ensure(named(reverse, "behind the world, off the HUD: hidden"), colourAt(40, 24) == unorm(MID_COLOUR)); - ensure(named(reverse, "in front of the world, off the HUD: shown"), colourAt(44, 20) == unorm(OVERLAY_COLOUR)); - ensure(named(reverse, "on the HUD: hidden behind it"), colourAt(24, 40) == unorm(HUD_COLOUR)); - ensure(named(reverse, "over sky, off the HUD: shown"), colourAt(40, 40) == unorm(OVERLAY_COLOUR)); + UNSCOPED_INFO("behind the world, off the HUD: hidden"); + REQUIRE(colourAt(40, 24) == unorm(MID_COLOUR)); + UNSCOPED_INFO("in front of the world, off the HUD: shown"); + REQUIRE(colourAt(44, 20) == unorm(OVERLAY_COLOUR)); + UNSCOPED_INFO("on the HUD: hidden behind it"); + REQUIRE(colourAt(24, 40) == unorm(HUD_COLOUR)); + UNSCOPED_INFO("over sky, off the HUD: shown"); + REQUIRE(colourAt(40, 40) == unorm(OVERLAY_COLOUR)); blit(); clearUnderHUD(); drawHUD(); overlay(40, 24, 8.f); - ensure(named(reverse, "unrestored, what the world hides shows through"), colourAt(40, 24) == unorm(OVERLAY_COLOUR)); + UNSCOPED_INFO("unrestored, what the world hides shows through"); + REQUIRE(colourAt(40, 24) == unorm(OVERLAY_COLOUR)); } } // Outside every rect nothing changes: the clear empties the rects alone, and with nothing drawn between, the // restore gives back exactly the depth the blit wrote. - template<> template<> - void alhuddepth_object_t::test<4>() + TEST_CASE_METHOD(alhuddepth_data, "ALHUDDepth: clear and restore change nothing outside the rects", "[ALHUDDepth]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); - ensure_equals(named(reverse, "one rect"), mRects.size(), size_t(1)); - ensure(named(reverse, "the rect is inside the window and not all of it"), - mRects[0].mLeft > 0 && mRects[0].mRight < W && mRects[0].mBottom > 0 && mRects[0].mTop < H); + REQUIRE(mRects.size() == size_t(1)); + { + INFO("the rect is inside the window and not all of it"); + REQUIRE(mRects[0].mLeft > 0); + REQUIRE(mRects[0].mRight < W); + REQUIRE(mRects[0].mBottom > 0); + REQUIRE(mRects[0].mTop < H); + } blit(); const std::vector before = depths(mWindow); @@ -513,8 +525,10 @@ namespace tut } } } - ensure_equals(named(reverse, "the clear leaves the outside alone"), changed_outside, size_t(0)); - ensure_equals(named(reverse, "and empties the inside"), uncleared_inside, size_t(0)); + UNSCOPED_INFO("the clear leaves the outside alone"); + REQUIRE(changed_outside == size_t(0)); + UNSCOPED_INFO("and empties the inside"); + REQUIRE(uncleared_inside == size_t(0)); restoreUnderHUD(); const std::vector restored = depths(mWindow); @@ -523,68 +537,79 @@ namespace tut { differing += restored[i] != before[i] ? 1 : 0; } - ensure_equals(named(reverse, "the restore gives back the blit's depth"), differing, size_t(0)); + UNSCOPED_INFO("the restore gives back the blit's depth"); + REQUIRE(differing == size_t(0)); } } // boxRect: the pixels a box covers through an orthographic projection, grown a pixel each way, clipped to the // viewport, empty off it. - template<> template<> - void alhuddepth_object_t::test<5>() + TEST_CASE_METHOD(alhuddepth_data, "ALHUDDepth: boxRect covers a box's pixels grown by one and clipped to the viewport", "[ALHUDDepth]") { const LLMatrix4a ortho = LLMatrix4a::ortho(-0.5f, 0.5f, -0.5f, 0.5f, 0.f, HUD_DEPTH); LLRect rect = ALHUDDepth::boxRect(ortho, LLVector3(-0.25f, -0.25f, -2.f), LLVector3(0.25f, 0.25f, 0.f), W, H); - ensure("pixels 16 to 48 grown to 15 to 49", rect == LLRect(15, 49, 49, 15)); + UNSCOPED_INFO("pixels 16 to 48 grown to 15 to 49"); + CHECK(rect == LLRect(15, 49, 49, 15)); // x from -0.1 to 0.13: pixels 25.6 to 40.32; y from 0 to 0.2: pixels 32 to 44.8. rect = ALHUDDepth::boxRect(ortho, LLVector3(-0.1f, 0.f, -1.f), LLVector3(0.13f, 0.2f, -1.f), W, H); - ensure("fractional edges floored and ceiled before the growth", rect == LLRect(24, 46, 42, 31)); + UNSCOPED_INFO("fractional edges floored and ceiled before the growth"); + CHECK(rect == LLRect(24, 46, 42, 31)); // x from 0.4 to 0.8: pixels 57.6 to 83.2. rect = ALHUDDepth::boxRect(ortho, LLVector3(0.4f, -0.1f, -1.f), LLVector3(0.8f, 0.1f, -1.f), W, H); - ensure("clipped to the viewport", rect == LLRect(56, 40, 64, 24)); + UNSCOPED_INFO("clipped to the viewport"); + CHECK(rect == LLRect(56, 40, 64, 24)); rect = ALHUDDepth::boxRect(ortho, LLVector3(0.6f, -0.1f, -1.f), LLVector3(0.9f, 0.1f, -1.f), W, H); - ensure("off the viewport, empty", rect.isEmpty()); + UNSCOPED_INFO("off the viewport, empty"); + CHECK(rect.isEmpty()); } // mergeRect: overlapping rects fold into one, disjoint ones are kept, and a rect bridging two folds them all. - template<> template<> - void alhuddepth_object_t::test<6>() + TEST_CASE_METHOD(alhuddepth_data, "ALHUDDepth: mergeRect folds overlapping rects and keeps disjoint ones apart", "[ALHUDDepth]") { std::vector rects; ALHUDDepth::mergeRect(rects, LLRect(0, 10, 10, 0)); ALHUDDepth::mergeRect(rects, LLRect(5, 15, 15, 5)); - ensure_equals("overlapping, one", rects.size(), size_t(1)); - ensure("their union", rects[0] == LLRect(0, 15, 15, 0)); + UNSCOPED_INFO("overlapping, one"); + REQUIRE(rects.size() == size_t(1)); + UNSCOPED_INFO("their union"); + CHECK(rects[0] == LLRect(0, 15, 15, 0)); ALHUDDepth::mergeRect(rects, LLRect(30, 40, 40, 30)); - ensure_equals("disjoint, kept apart", rects.size(), size_t(2)); + UNSCOPED_INFO("disjoint, kept apart"); + REQUIRE(rects.size() == size_t(2)); ALHUDDepth::mergeRect(rects, LLRect(14, 31, 31, 14)); - ensure_equals("bridged, one", rects.size(), size_t(1)); - ensure("around all three", rects[0] == LLRect(0, 40, 40, 0)); + UNSCOPED_INFO("bridged, one"); + REQUIRE(rects.size() == size_t(1)); + UNSCOPED_INFO("around all three"); + CHECK(rects[0] == LLRect(0, 40, 40, 0)); } // Without the copy program to put the scene's depth back, the depth under the HUD is not cleared: the HUD tests // against the scene's as before, and what the world hides from the 3D UI after it stays hidden. - template<> template<> - void alhuddepth_object_t::test<7>() + TEST_CASE_METHOD(alhuddepth_data, "ALHUDDepth: without the copy program the depth under the HUD is not cleared", "[ALHUDDepth]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); LLGLSLShader unloaded; blit(); const std::vector before = depths(mWindow); clearUnderHUD(&unloaded); - ensure(named(reverse, "nothing cleared"), depths(mWindow) == before); + UNSCOPED_INFO("nothing cleared"); + REQUIRE(depths(mWindow) == before); drawHUD(); restoreUnderHUD(&unloaded); overlay(40, 24, 8.f); - ensure(named(reverse, "behind the world under the HUD's rect: hidden"), colourAt(40, 24) == unorm(MID_COLOUR)); - ensure(named(reverse, "the near world still hides the HUD"), colourAt(24, 24) == unorm(NEAR_COLOUR)); + UNSCOPED_INFO("behind the world under the HUD's rect: hidden"); + REQUIRE(colourAt(40, 24) == unorm(MID_COLOUR)); + UNSCOPED_INFO("the near world still hides the HUD"); + REQUIRE(colourAt(24, 24) == unorm(NEAR_COLOUR)); } } } diff --git a/indra/newview/tests/almotionpredictor_test.cpp b/indra/newview/tests/almotionpredictor_test.cpp index 551e131cf6..18ec12c518 100644 --- a/indra/newview/tests/almotionpredictor_test.cpp +++ b/indra/newview/tests/almotionpredictor_test.cpp @@ -14,13 +14,14 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include +#include "altestmatchers.h" #include "../almotionpredictor.h" #include -namespace tut +namespace { struct predictor_data { @@ -67,79 +68,66 @@ namespace tut } }; - typedef test_group predictor_group_t; - typedef predictor_group_t::object predictor_object_t; - tut::predictor_group_t predictor_group("ALMotionPredictor"); - // ---------------------------------------------------------------- cadence - template<> template<> - void predictor_object_t::test<1>() + TEST_CASE_METHOD(predictor_data, "ALMotionPredictor: one update establishes no rate", "[ALMotionPredictor]") { - set_test_name("one update establishes no rate"); - - ensure("nothing seen yet", !mPredictor.hasCadence()); + UNSCOPED_INFO("nothing seen yet"); + CHECK_FALSE(mPredictor.hasCadence()); mPredictor.noteUpdate(100.0); - ensure("a single update is a timestamp, not a rate", !mPredictor.hasCadence()); + UNSCOPED_INFO("a single update is a timestamp, not a rate"); + CHECK_FALSE(mPredictor.hasCadence()); mPredictor.noteUpdate(100.1); - ensure("a second update gives a rate", mPredictor.hasCadence()); - ensure_approximately_equals("interval is the observed gap", - mPredictor.getUpdateInterval(), 0.1f, 16); + UNSCOPED_INFO("a second update gives a rate"); + CHECK(mPredictor.hasCadence()); + UNSCOPED_INFO("interval is the observed gap"); + CHECK_THAT(mPredictor.getUpdateInterval(), ll_test::WithinBits(0.1f, 16)); } - template<> template<> - void predictor_object_t::test<2>() + TEST_CASE_METHOD(predictor_data, "ALMotionPredictor: cadence converges on a steady rate", "[ALMotionPredictor]") { - set_test_name("cadence converges on a steady rate"); - feedCadence(0.1f); - ensure_approximately_equals("10 Hz", mPredictor.getUpdateInterval(), 0.1f, 16); + CHECK_THAT(mPredictor.getUpdateInterval(), ll_test::WithinBits(0.1f, 16)); ALMotionPredictor slow; // Drive the second one directly so both start clean. F64 t = 100.0; for (S32 i = 0; i < 12; ++i) { slow.noteUpdate(t); t += 1.0; } - ensure_approximately_equals("1 Hz", slow.getUpdateInterval(), 1.0f, 16); + CHECK_THAT(slow.getUpdateInterval(), ll_test::WithinBits(1.0f, 16)); } - template<> template<> - void predictor_object_t::test<3>() + TEST_CASE_METHOD(predictor_data, "ALMotionPredictor: silence is not folded back into the rate", "[ALMotionPredictor]") { - set_test_name("silence is not folded back into the rate"); - feedCadence(0.1f); const F32 before = mPredictor.getUpdateInterval(); // The gap the window exists to detect must not move the threshold to wherever the object // already is, or an object can never be late. mPredictor.noteUpdate(200.0); - ensure_equals("a long gap is not evidence about a send rate", - mPredictor.getUpdateInterval(), before); + UNSCOPED_INFO("a long gap is not evidence about a send rate"); + CHECK(mPredictor.getUpdateInterval() == before); mPredictor.forgetCadence(); - ensure("a handoff clears it", !mPredictor.hasCadence()); + UNSCOPED_INFO("a handoff clears it"); + CHECK_FALSE(mPredictor.hasCadence()); } // ----------------------------------------------------------------- window - template<> template<> - void predictor_object_t::test<4>() + TEST_CASE_METHOD(predictor_data, "ALMotionPredictor: no observed cadence means no taper", "[ALMotionPredictor]") { - set_test_name("no observed cadence means no taper"); - mTuning.mCadenceAware = true; const ALMotionPredictor::Window w = mPredictor.phaseOutWindow(mTuning); - ensure("a ballistic object the sim never corrects keeps coasting", !w.isTapering()); - ensure_equals("and keeps its full step", ALMotionPredictor::phaseOutFactor(60.f, w), 1.f); + UNSCOPED_INFO("a ballistic object the sim never corrects keeps coasting"); + CHECK_FALSE(w.isTapering()); + UNSCOPED_INFO("and keeps its full step"); + CHECK(ALMotionPredictor::phaseOutFactor(60.f, w) == 1.f); } - template<> template<> - void predictor_object_t::test<5>() + TEST_CASE_METHOD(predictor_data, "ALMotionPredictor: window scales with the object's own update rate", "[ALMotionPredictor]") { - set_test_name("window scales with the object's own update rate"); - mTuning.mCadenceAware = true; struct { F32 mInterval; F32 mStart; F32 mEnd; } cases[] = { { 0.1f, 1.f, 3.f }, // ~10 Hz: floored at the configured phase-out time @@ -154,18 +142,17 @@ namespace tut for (S32 i = 0; i < 12; ++i) { p.noteUpdate(t); t += c.mInterval; } const ALMotionPredictor::Window w = p.phaseOutWindow(mTuning); - ensure("tapers", w.isTapering()); - ensure_approximately_equals("start", w.mStart, c.mStart, 16); - ensure_approximately_equals("end", w.mEnd, c.mEnd, 16); - ensure("taper span is preserved", w.mEnd - w.mStart >= 2.f - 1e-4f); + CAPTURE(c.mInterval); + REQUIRE(w.isTapering()); + REQUIRE_THAT(w.mStart, ll_test::WithinBits(c.mStart, 16)); + REQUIRE_THAT(w.mEnd, ll_test::WithinBits(c.mEnd, 16)); + UNSCOPED_INFO("taper span is preserved"); + REQUIRE(w.mEnd - w.mStart >= 2.f - 1e-4f); } } - template<> template<> - void predictor_object_t::test<6>() + TEST_CASE_METHOD(predictor_data, "ALMotionPredictor: a very slow mover is still bounded by the cap", "[ALMotionPredictor]") { - set_test_name("a very slow mover is still bounded by the cap"); - ALMotionPredictor p; F64 t = 100.0; for (S32 i = 0; i < 12; ++i) { p.noteUpdate(t); t += 2.4; } // just inside credible @@ -175,41 +162,39 @@ namespace tut mTuning.mCadenceFactor = 8.f; const ALMotionPredictor::Window w = p.phaseOutWindow(mTuning); - ensure("tapers", w.isTapering()); - ensure_approximately_equals("clamped to the cap", w.mStart, mTuning.mCadenceCap, 16); - ensure_approximately_equals("and the taper still follows it", - w.mEnd, mTuning.mCadenceCap + 2.f, 16); + CHECK(w.isTapering()); + CHECK_THAT(w.mStart, ll_test::WithinBits(mTuning.mCadenceCap, 16)); + UNSCOPED_INFO("and the taper still follows it"); + CHECK_THAT(w.mEnd, ll_test::WithinBits(mTuning.mCadenceCap + 2.f, 16)); } - template<> template<> - void predictor_object_t::test<7>() + TEST_CASE_METHOD(predictor_data, "ALMotionPredictor: legacy mode ignores cadence entirely", "[ALMotionPredictor]") { - set_test_name("legacy mode ignores cadence entirely"); - // No cadence observed at all, yet the window still exists -- that is the default. const ALMotionPredictor::Window w = mPredictor.phaseOutWindow(mTuning); - ensure("tapers", w.isTapering()); - ensure_approximately_equals("start", w.mStart, 1.f, 4); - ensure_approximately_equals("end", w.mEnd, 3.f, 4); + CHECK(w.isTapering()); + CHECK_THAT(w.mStart, ll_test::WithinBits(1.f, 4)); + CHECK_THAT(w.mEnd, ll_test::WithinBits(3.f, 4)); } // ------------------------------------------------------------------- ramp - template<> template<> - void predictor_object_t::test<8>() + TEST_CASE_METHOD(predictor_data, "ALMotionPredictor: the phase-out ramp is continuous and monotone and bounded", "[ALMotionPredictor]") { - set_test_name("ramp is continuous, monotone and bounded"); - ALMotionPredictor::Window w; w.mStart = 1.f; w.mEnd = 3.f; - ensure_equals("full before the window", ALMotionPredictor::phaseOutFactor(0.5f, w), 1.f); - ensure_equals("full at the start", ALMotionPredictor::phaseOutFactor(1.f, w), 1.f); - ensure_equals("stopped at the end", ALMotionPredictor::phaseOutFactor(3.f, w), 0.f); - ensure_equals("stopped after it", ALMotionPredictor::phaseOutFactor(9.f, w), 0.f); - ensure_approximately_equals("halfway", - ALMotionPredictor::phaseOutFactor(2.f, w), 0.5f, 16); + UNSCOPED_INFO("full before the window"); + CHECK(ALMotionPredictor::phaseOutFactor(0.5f, w) == 1.f); + UNSCOPED_INFO("full at the start"); + CHECK(ALMotionPredictor::phaseOutFactor(1.f, w) == 1.f); + UNSCOPED_INFO("stopped at the end"); + CHECK(ALMotionPredictor::phaseOutFactor(3.f, w) == 0.f); + UNSCOPED_INFO("stopped after it"); + CHECK(ALMotionPredictor::phaseOutFactor(9.f, w) == 0.f); + UNSCOPED_INFO("halfway"); + CHECK_THAT(ALMotionPredictor::phaseOutFactor(2.f, w), ll_test::WithinBits(0.5f, 16)); // The step the two-branch form put in was about a third of full speed. Walking the ramp at // 240 fps, no single frame may drop by anything like that. @@ -219,54 +204,48 @@ namespace tut for (F32 t = 0.f; t < 4.f; t += dt) { const F32 v = ALMotionPredictor::phaseOutFactor(t, w); - ensure("monotone non-increasing", v <= prev + 1e-6f); + CAPTURE(t); + REQUIRE(v <= prev + 1e-6f); worst = llmax(worst, prev - v); prev = v; } - ensure("no step in the curve", worst < 0.01f); + UNSCOPED_INFO("no step in the curve"); + CHECK(worst < 0.01f); } // --------------------------------------------------------------- dt clamp - template<> template<> - void predictor_object_t::test<9>() + TEST_CASE_METHOD(predictor_data, "ALMotionPredictor: a stalled frame cannot carry an object the whole stall", "[ALMotionPredictor]") { - set_test_name("a stalled frame cannot carry an object the whole stall"); - - ensure_approximately_equals("an ordinary frame passes through", - ALMotionPredictor::clampFrameStep(1.f / 60.f, mTuning), - 1.f / 60.f, 5); - ensure_equals("a 30 s pause is bounded", - ALMotionPredictor::clampFrameStep(30.f, mTuning), mTuning.mMaxFrameStep); - ensure_equals("time never runs backwards", - ALMotionPredictor::clampFrameStep(-1.f, mTuning), 0.f); + UNSCOPED_INFO("an ordinary frame passes through"); + CHECK_THAT(ALMotionPredictor::clampFrameStep(1.f / 60.f, mTuning), + ll_test::WithinBits(1.f / 60.f, 5)); + UNSCOPED_INFO("a 30 s pause is bounded"); + CHECK(ALMotionPredictor::clampFrameStep(30.f, mTuning) == mTuning.mMaxFrameStep); + UNSCOPED_INFO("time never runs backwards"); + CHECK(ALMotionPredictor::clampFrameStep(-1.f, mTuning) == 0.f); mTuning.mMaxFrameStep = 0.f; - ensure_equals("zero disables the clamp", - ALMotionPredictor::clampFrameStep(30.f, mTuning), 30.f); + UNSCOPED_INFO("zero disables the clamp"); + CHECK(ALMotionPredictor::clampFrameStep(30.f, mTuning) == 30.f); } // ------------------------------------------------------------ integration - template<> template<> - void predictor_object_t::test<10>() + TEST_CASE_METHOD(predictor_data, "ALMotionPredictor: integration is independent of frame rate", "[ALMotionPredictor]") { - set_test_name("integration is independent of frame rate"); - const F32 reference = drop(60.f, 1.f); const F32 fps[] = { 30.f, 45.f, 90.f, 120.f, 144.f, 240.f, 300.f }; for (F32 f : fps) { - ensure_approximately_equals("same fall at any frame rate", drop(f, 1.f), reference, 8); + CAPTURE(f); + REQUIRE_THAT(drop(f, 1.f), ll_test::WithinBits(reference, 8)); } } - template<> template<> - void predictor_object_t::test<11>() + TEST_CASE_METHOD(predictor_data, "ALMotionPredictor: converting once reproduces the old per-frame form exactly", "[ALMotionPredictor]") { - set_test_name("converting once reproduces the old per-frame form exactly"); - // The viewer used to fold the timestep correction into every frame as // (vel + 0.5 * (dt - PHYSICS_TIMESTEP) * accel) * dt // rather than converting the velocity once on arrival. Expanding both shows they are the @@ -297,16 +276,13 @@ namespace tut new_vel += accel * dt; } - ensure_approximately_equals("same trajectory as the form it replaces", - new_pos, old_pos, 20); + CAPTURE(f); + REQUIRE_THAT(new_pos, ll_test::WithinBits(old_pos, 20)); } } - template<> template<> - void predictor_object_t::test<12>() + TEST_CASE_METHOD(predictor_data, "ALMotionPredictor: the timestep correction currently opposes the acceleration", "[ALMotionPredictor]") { - set_test_name("the timestep correction currently opposes the acceleration"); - // Characterisation, not endorsement. With the direction the viewer has always used, a // dropped object is handed an initial velocity *against* gravity of half a timestep's // worth -- so for the first ~11 ms it rises, and whether any given frame shows that @@ -323,36 +299,33 @@ namespace tut drop(30.f, 0.2f, &first_at_30); drop(60.f, 0.2f, &first_at_60); - ensure("first frame is downward when the frame outlasts the correction", - first_at_30 < 0.f); - ensure("and upward when it does not -- this is the open question, not a feature", - first_at_60 > 0.f); + UNSCOPED_INFO("first frame is downward when the frame outlasts the correction"); + CHECK(first_at_30 < 0.f); + UNSCOPED_INFO("and upward when it does not -- this is the open question, not a feature"); + CHECK(first_at_60 > 0.f); const LLVector3 accel(0.f, 0.f, GRAVITY); const LLVector3 converted = ALMotionPredictor::finalVelocity(LLVector3::zero, accel); - ensure("the offset opposes the acceleration", converted.mV[VZ] * GRAVITY < 0.f); + UNSCOPED_INFO("the offset opposes the acceleration"); + CHECK(converted.mV[VZ] * GRAVITY < 0.f); } - template<> template<> - void predictor_object_t::test<13>() + TEST_CASE_METHOD(predictor_data, "ALMotionPredictor: the sim timestep correction is small and only along the acceleration", "[ALMotionPredictor]") { - set_test_name("the sim timestep correction is applied once, and is small"); - const LLVector3 accel(0.f, 0.f, GRAVITY); const LLVector3 converted = ALMotionPredictor::finalVelocity(LLVector3::zero, accel); // Half a timestep of gravity, ~0.109 m/s. Direction is deliberately whatever the viewer // has always used; see SIGN_AVERAGE_TO_FINAL. - ensure_approximately_equals("offset magnitude", - fabsf(converted.mV[VZ]), - fabsf(0.5f * GRAVITY * ALMotionPredictor::SIM_TIMESTEP), 16); - ensure("horizontal axes untouched", - converted.mV[VX] == 0.f && converted.mV[VY] == 0.f); + CHECK_THAT(fabsf(converted.mV[VZ]), + ll_test::WithinBits(fabsf(0.5f * GRAVITY * ALMotionPredictor::SIM_TIMESTEP), 16)); + CHECK(converted.mV[VX] == 0.f); + CHECK(converted.mV[VY] == 0.f); // With no acceleration there is nothing to convert, which is the overwhelmingly common // case -- an avatar walking, a scripted mover at constant velocity. const LLVector3 walking(3.2f, 0.f, 0.f); - ensure_equals("unaccelerated velocity passes through untouched", - ALMotionPredictor::finalVelocity(walking, LLVector3::zero), walking); + UNSCOPED_INFO("unaccelerated velocity passes through untouched"); + CHECK(ALMotionPredictor::finalVelocity(walking, LLVector3::zero) == walking); } } diff --git a/indra/newview/tests/alnotecardembedded_test.cpp b/indra/newview/tests/alnotecardembedded_test.cpp index 995e21ea10..907946fce4 100644 --- a/indra/newview/tests/alnotecardembedded_test.cpp +++ b/indra/newview/tests/alnotecardembedded_test.cpp @@ -40,7 +40,7 @@ #include "../../llui/tests/alheadlessui_fixture.h" -#include "../test/lltut.h" +#include #include #include @@ -99,7 +99,7 @@ namespace }; } -namespace tut +namespace { struct alnotecardembedded_data { @@ -125,7 +125,7 @@ namespace tut { if (!ll_test::HeadlessUI::get().ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } notecard.object.generate(); notecard.item.generate(); @@ -183,123 +183,162 @@ namespace tut return accept; } }; - typedef test_group alnotecardembedded_group; - typedef alnotecardembedded_group::object alnotecardembedded_object; - alnotecardembedded_group alnotecardembedded_instance("ALNotecardEmbedded"); - template<> template<> - void alnotecardembedded_object::test<1>() + TEST_CASE_METHOD(alnotecardembedded_data, "ALNotecardEmbedded: a save sends only the items the text stands and renumbers them in order", "[ALNotecardEmbedded]") { - set_test_name("a save sends only the items the text still stands, numbered afresh in the order it stands them"); const auto a = item("A"), b = item("B"), c = item("C"); ALNotecardEmbedded& card = make("one " + at(2) + " two " + at(0) + "\n", { a, b, c }); - ensure("a button over each item the text stands", view->atomAt(ALTextPos(0, 4)) != nullptr); + UNSCOPED_INFO("a button over each item the text stands"); + REQUIRE(view->atomAt(ALTextPos(0, 4)) != nullptr); std::string text; ALNotecardEmbedded::items_t items; card.forSave(text, items); - ensure_equals("renumbered", text, "one " + at(0) + " two " + at(1) + "\n"); - ensure("C, then A; B left behind", items.size() == 2 && items[0] == c && items[1] == a); - ensure_equals("the editor's own text as it was", view->text(), "one " + at(2) + " two " + at(0) + "\n"); + UNSCOPED_INFO("renumbered"); + CHECK(text == "one " + at(0) + " two " + at(1) + "\n"); + { + INFO("C, then A; B left behind"); + REQUIRE(items.size() == 2); + CHECK(items[0] == c); + CHECK(items[1] == a); + } + UNSCOPED_INFO("the editor's own text as it was"); + CHECK(view->text() == "one " + at(2) + " two " + at(0) + "\n"); } - template<> template<> - void alnotecardembedded_object::test<2>() + TEST_CASE_METHOD(alnotecardembedded_data, "ALNotecardEmbedded: items dropped together land in a row and a later drop at the pointer", "[ALNotecardEmbedded]") { - set_test_name("several items dropped together land one after another; a later drop lands at the pointer"); const auto a = item("A"), b = item("B"), c = item("C"); make("text\n"); const LLRect rect = view->textRect(); const ALTextPos where = view->posAtLocal(rect.mLeft + 1, rect.mTop - 2, true); - ensure("taken", drop(a, true) == ACCEPT_YES_COPY_MULTI); - ensure("taken", drop(b, true) == ACCEPT_YES_COPY_MULTI); + CHECK(drop(a, true) == ACCEPT_YES_COPY_MULTI); + CHECK(drop(b, true) == ACCEPT_YES_COPY_MULTI); const std::string line = view->document().line(where.line); - ensure_equals("A, then B after it", line.substr(where.column, 8), at(0) + at(1)); - ensure("each with its button", view->atomAt(ALTextPos(where.line, where.column)) && view->atomAt(ALTextPos(where.line, where.column + 4))); + UNSCOPED_INFO("A, then B after it"); + CHECK(line.substr(where.column, 8) == at(0) + at(1)); + { + INFO("each with its button"); + REQUIRE(view->atomAt(ALTextPos(where.line, where.column))); + REQUIRE(view->atomAt(ALTextPos(where.line, where.column + 4))); + } world.frameNow = 2; drop(c, true); - ensure_equals("a new frame's drop lands where the pointer is", view->document().line(where.line).substr(where.column, 4), at(2)); - ensure_equals("three carried", tab->items().size(), size_t(3)); - ensure("one undo takes the last away", view->undoJournal().canUndo()); + UNSCOPED_INFO("a new frame's drop lands where the pointer is"); + CHECK(view->document().line(where.line).substr(where.column, 4) == at(2)); + REQUIRE(tab->items().size() == size_t(3)); + UNSCOPED_INFO("one undo takes the last away"); + CHECK(view->undoJournal().canUndo()); } - template<> template<> - void alnotecardembedded_object::test<3>() + TEST_CASE_METHOD(alnotecardembedded_data, "ALNotecardEmbedded: a drop of what a notecard may not carry is refused and says why", "[ALNotecardEmbedded]") { - set_test_name("a drop is refused, and says why, for what a notecard may not carry"); make("text\n"); std::string tip; - ensure("not whole to the next owner", drop(item("A", false), false, &tip) == ACCEPT_NO && !tip.empty()); + { + INFO("not whole to the next owner"); + CHECK(drop(item("A", false), false, &tip) == ACCEPT_NO); + REQUIRE_FALSE(tip.empty()); + } world.copyable = false; - ensure("not the agent's to copy", drop(item("B"), false, &tip) == ACCEPT_NO); - ensure("said so", !tip.empty()); + UNSCOPED_INFO("not the agent's to copy"); + CHECK(drop(item("B"), false, &tip) == ACCEPT_NO); + UNSCOPED_INFO("said so"); + REQUIRE_FALSE(tip.empty()); world.copyable = true; world.settings = false; - ensure("settings where the grid has none", drop(item("C"), false, &tip, DAD_SETTINGS) == ACCEPT_NO); + UNSCOPED_INFO("settings where the grid has none"); + CHECK(drop(item("C"), false, &tip, DAD_SETTINGS) == ACCEPT_NO); world.fromNotecard = true; EAcceptance accept = ACCEPT_YES_COPY_MULTI; std::string ignored; - ensure("out of another notecard: left to someone else", !tab->drop(1, 1, false, DAD_NOTECARD, item("D").get(), &accept, ignored)); + UNSCOPED_INFO("out of another notecard: left to someone else"); + CHECK_FALSE(tab->drop(1, 1, false, DAD_NOTECARD, item("D").get(), &accept, ignored)); world.fromNotecard = false; changeable = false; - ensure("a notecard that may not be changed", drop(item("E"), false, &tip) == ACCEPT_NO); - ensure("said so", !tip.empty()); - ensure("nothing taken", tab->items().empty()); + UNSCOPED_INFO("a notecard that may not be changed"); + CHECK(drop(item("E"), false, &tip) == ACCEPT_NO); + UNSCOPED_INFO("said so"); + REQUIRE_FALSE(tip.empty()); + UNSCOPED_INFO("nothing taken"); + REQUIRE(tab->items().empty()); } - template<> template<> - void alnotecardembedded_object::test<4>() + TEST_CASE_METHOD(alnotecardembedded_data, "ALNotecardEmbedded: only the saved asset's items copy out and a late answer does nothing", "[ALNotecardEmbedded]") { - set_test_name("only what the saved asset carries is copied out; a save changes what that is; an answer after the tab has gone does nothing"); const auto saved = item("Saved"), dropped = item("Dropped"); ALNotecardEmbedded& card = make(at(0) + "\n", { saved }); drop(dropped, true); - ensure("a drop not saved: refused", !card.copy(dropped, LLUUID::null)); - ensure("said so, as an error", !said.empty() && said.back().second && said.back().first.find("Dropped") != std::string::npos); - ensure("nothing asked of the region", world.copies.empty()); - ensure("one saved: asked", card.copy(saved, LLUUID::null) && world.copies.size() == 1 && world.copies.back() == saved->getUUID()); + UNSCOPED_INFO("a drop not saved: refused"); + CHECK_FALSE(card.copy(dropped, LLUUID::null)); + { + INFO("said so, as an error"); + REQUIRE_FALSE(said.empty()); + CHECK(said.back().second); + REQUIRE(said.back().first.find("Dropped") != std::string::npos); + } + UNSCOPED_INFO("nothing asked of the region"); + REQUIRE(world.copies.empty()); + { + INFO("one saved: asked"); + CHECK(card.copy(saved, LLUUID::null)); + REQUIRE(world.copies.size() == 1); + CHECK(world.copies.back() == saved->getUUID()); + } world.refused("no such item"); - ensure("the region's no, said", said.back().second && said.back().first.find("Saved") != std::string::npos && - said.back().first.find("no such item") != std::string::npos); + { + INFO("the region's no, said"); + CHECK(said.back().second); + REQUIRE(said.back().first.find("Saved") != std::string::npos); + REQUIRE(said.back().first.find("no such item") != std::string::npos); + } // Saved with only the drop in it. card.saved({ dropped->getUUID() }); - ensure("the drop now", card.copy(dropped, LLUUID::null)); - ensure("the old one no longer", !card.copy(saved, LLUUID::null)); + UNSCOPED_INFO("the drop now"); + CHECK(card.copy(dropped, LLUUID::null)); + UNSCOPED_INFO("the old one no longer"); + CHECK_FALSE(card.copy(saved, LLUUID::null)); // Pressed: offered, where the world does not take it all. card.open(dropped); - ensure("asked whether to copy", world.confirms.size() == 1); + UNSCOPED_INFO("asked whether to copy"); + REQUIRE(world.confirms.size() == 1); world.confirms.back()(); - ensure("and copied on yes", world.copies.back() == dropped->getUUID()); + UNSCOPED_INFO("and copied on yes"); + CHECK(world.copies.back() == dropped->getUUID()); const size_t copies = world.copies.size(); card.open(saved); - ensure("not offered for what cannot be copied", world.confirms.size() == 1); + UNSCOPED_INFO("not offered for what cannot be copied"); + REQUIRE(world.confirms.size() == 1); // The tab gone before the answers: nothing happens. card.open(dropped); const std::function late = world.confirms.back(); tab.reset(); late(); - ensure("no copy asked for a tab that has gone", world.copies.size() == copies); + UNSCOPED_INFO("no copy asked for a tab that has gone"); + REQUIRE(world.copies.size() == copies); } - template<> template<> - void alnotecardembedded_object::test<5>() + TEST_CASE_METHOD(alnotecardembedded_data, "ALNotecardEmbedded: asLLSD and fromLLSD keep each item in its place and a gap as a gap", "[ALNotecardEmbedded]") { - set_test_name("items kept and read back in their places"); const auto a = item("A"), b = item("B"); const LLSD kept = ALNotecardEmbedded::asLLSD({ a, LLPointer(), b }); const ALNotecardEmbedded::items_t back = ALNotecardEmbedded::fromLLSD(kept); - ensure_equals("each in its place", back.size(), size_t(3)); - ensure("a missing one stays missing", back[1].isNull()); - ensure("the rest as they were", back[0]->getUUID() == a->getUUID() && back[2]->getName() == "B"); + UNSCOPED_INFO("each in its place"); + REQUIRE(back.size() == size_t(3)); + UNSCOPED_INFO("a missing one stays missing"); + REQUIRE(back[1].isNull()); + { + INFO("the rest as they were"); + CHECK(back[0]->getUUID() == a->getUUID()); + CHECK(back[2]->getName() == "B"); + } } - template<> template<> - void alnotecardembedded_object::test<6>() + TEST_CASE_METHOD(alnotecardembedded_data, "ALNotecardEmbedded: an item button dragged far drags a saved item out but not an unsaved one", "[ALNotecardEmbedded]") { - set_test_name("an item's button dragged far enough drags the item out of the notecard; one not saved yet is said to need a save"); const auto saved = item("Saved"), dropped = item("Dropped"); make(at(0) + "\n", { saved }); drop(dropped, true); @@ -316,41 +355,66 @@ namespace tut return nullptr; }; LLButton* button = buttonOf(0); - ensure("a button over the saved item", button != nullptr); + UNSCOPED_INFO("a button over the saved item"); + REQUIRE(button != nullptr); button->handleMouseDown(2, 2, MASK_NONE); - ensure("the press measured from", world.pressed); + UNSCOPED_INFO("the press measured from"); + CHECK(world.pressed); button->handleHover(3, 3, MASK_NONE); - ensure("not far enough: no drag", world.dragged.empty()); + UNSCOPED_INFO("not far enough: no drag"); + REQUIRE(world.dragged.empty()); world.past = true; button->handleHover(20, 3, MASK_NONE); - ensure("far enough: dragged out", world.dragged.size() == 1 && world.dragged.back() == saved->getUUID()); - ensure("out of this notecard", world.draggedFrom.item == notecard.item && world.draggedFrom.object == notecard.object); + { + INFO("far enough: dragged out"); + REQUIRE(world.dragged.size() == 1); + CHECK(world.dragged.back() == saved->getUUID()); + } + { + INFO("out of this notecard"); + CHECK(world.draggedFrom.item == notecard.item); + CHECK(world.draggedFrom.object == notecard.object); + } button->handleMouseUp(20, 3, MASK_NONE); LLButton* unsaved = buttonOf(1); - ensure("a button over the drop", unsaved != nullptr); + UNSCOPED_INFO("a button over the drop"); + REQUIRE(unsaved != nullptr); said.clear(); unsaved->handleMouseDown(2, 2, MASK_NONE); unsaved->handleHover(20, 3, MASK_NONE); - ensure("a drop not saved is not dragged out", world.dragged.size() == 1); - ensure("and a save is said to be needed", !said.empty() && said.back().second && said.back().first.find("Dropped") != std::string::npos); + UNSCOPED_INFO("a drop not saved is not dragged out"); + REQUIRE(world.dragged.size() == 1); + { + INFO("and a save is said to be needed"); + REQUIRE_FALSE(said.empty()); + CHECK(said.back().second); + REQUIRE(said.back().first.find("Dropped") != std::string::npos); + } unsaved->handleMouseUp(20, 3, MASK_NONE); } - template<> template<> - void alnotecardembedded_object::test<7>() + TEST_CASE_METHOD(alnotecardembedded_data, "ALNotecardEmbedded: a line holding an item grows to fit its icon and its neighbours do not", "[ALNotecardEmbedded]") { - set_test_name("a line holding an item is tall enough for its icon inside the chip's frame; the lines around it keep the text's height"); ALNotecardEmbedded& card = make("above\nhere " + at(0) + " it is\nbelow\n", { item("A") }); (void)card; const S32 text_h = view->layout().rowHeight(); - ensure("a row an inventory icon is taller than: " + std::to_string(text_h), text_h < 16 + 4); + UNSCOPED_INFO("a row an inventory icon is taller than: " << text_h); + CHECK(text_h < 16 + 4); view->placeAtomViews(); - ensure_equals("the item's line: the icon, a pixel either side, and a pixel off the rows", view->layout().lineHeight(1), 16 + 4); - ensure_equals("the line above as it was", view->layout().lineHeight(0), text_h); - ensure_equals("and the line below", view->layout().lineHeight(2), text_h); + UNSCOPED_INFO("the item's line: the icon, a pixel either side, and a pixel off the rows"); + CHECK(view->layout().lineHeight(1) == 16 + 4); + UNSCOPED_INFO("the line above as it was"); + CHECK(view->layout().lineHeight(0) == text_h); + UNSCOPED_INFO("and the line below"); + CHECK(view->layout().lineHeight(2) == text_h); const ALTextView::Atom* atom = view->atomAt(ALTextPos(1, 5)); - ensure("the item's button", atom && atom->view); - ensure_equals("as tall as its line", atom->view->getRect().getHeight(), 16 + 4); + { + INFO("the item's button"); + REQUIRE(atom); + REQUIRE(atom->view); + } + UNSCOPED_INFO("as tall as its line"); + CHECK(atom->view->getRect().getHeight() == 16 + 4); } } diff --git a/indra/newview/tests/alrecoverykeeper_test.cpp b/indra/newview/tests/alrecoverykeeper_test.cpp index a81229a4cd..c08fe7f254 100644 --- a/indra/newview/tests/alrecoverykeeper_test.cpp +++ b/indra/newview/tests/alrecoverykeeper_test.cpp @@ -30,13 +30,14 @@ #include "fsyspath.h" #include "llfile.h" +#include "newview_test_fakes.h" -#include "../test/lltut.h" +#include #include #include -namespace tut +namespace { struct alrecoverykeeper_data { @@ -53,6 +54,7 @@ namespace tut { folder = fsyspath(std::filesystem::temp_directory_path() / fsyspath("alrecoverykeeper_" + LLUUID::generateNewID().asString())).string(); std::filesystem::create_directories(fsyspath(folder)); + newview_test::useRecoveryWriter(); store = std::make_unique(folder, "this-session"); ALRecovery::useStore(store.get()); } @@ -95,55 +97,61 @@ namespace tut return std::nullopt; } }; - typedef test_group alrecoverykeeper_group; - typedef alrecoverykeeper_group::object alrecoverykeeper_object; - alrecoverykeeper_group alrecoverykeeper_instance("ALRecoveryKeeper"); - template<> template<> - void alrecoverykeeper_object::test<1>() + TEST_CASE_METHOD(alrecoverykeeper_data, "ALRecoveryKeeper: unsaved text is written a moment after it changes until it is saved", "[ALRecoveryKeeper]") { - set_test_name("written a moment after it first changed, under the window's own key, again for each change, and forgotten once saved"); ALRecoveryKeeper keeper(holder()); keeper.setItem(object, item); - ensure("the window's own key", ALRecoveryStore::isWindowKey(keeper.key())); + CHECK(ALRecoveryStore::isWindowKey(keeper.key())); keeper.pump(1, 10.0); - ensure("nothing unsaved, nothing written", !mine()); + UNSCOPED_INFO("nothing unsaved, nothing written"); + CHECK_FALSE(mine()); unsaved = true; text = "typed"; keeper.pump(2, 10.0); - ensure("not at once", !mine()); + UNSCOPED_INFO("not at once"); + CHECK_FALSE(mine()); keeper.pump(3, 10.5); keeper.pump(3, 10.0 + ALRecoveryKeeper::DELAY); - ensure("a moment after it first changed, whatever was typed meanwhile", mine() && mine()->text == "typed" && mine()->name == "Card"); + { + INFO("a moment after it first changed, whatever was typed meanwhile"); + REQUIRE(mine()); + CHECK(mine()->text == "typed"); + CHECK(mine()->name == "Card"); + } text = "typed more"; keeper.pump(4, 12.0); keeper.pump(4, 12.0 + ALRecoveryKeeper::DELAY); - ensure("again for the next change", mine() && mine()->text == "typed more"); + { + INFO("again for the next change"); + REQUIRE(mine()); + CHECK(mine()->text == "typed more"); + } unsaved = false; keeper.pump(4, 20.0); - ensure("saved: forgotten", !mine()); - ensure("nothing failed", failed == 0); + UNSCOPED_INFO("saved: forgotten"); + CHECK_FALSE(mine()); + CHECK(failed == 0); } - template<> template<> - void alrecoverykeeper_object::test<2>() + TEST_CASE_METHOD(alrecoverykeeper_data, "ALRecoveryKeeper: setAside discards and what another session left is taken up or turned down", "[ALRecoveryKeeper]") { - set_test_name("thrown away: set aside, and the window's own gone; what another session left offered, taken up once the window's own is written, or turned down"); ALRecoveryKeeper keeper(holder()); keeper.setItem(object, item); unsaved = true; text = "thrown away"; keeper.pump(1, 0.0); keeper.pump(1, ALRecoveryKeeper::DELAY); - ensure("written", mine().has_value()); - ensure("set aside", keeper.setAside()); - ensure("its own gone", !mine()); + REQUIRE(mine().has_value()); + CHECK(keeper.setAside()); + UNSCOPED_INFO("its own gone"); + CHECK_FALSE(mine()); bool discarded = false; for (const ALRecoveryEntry& entry : store->list()) { discarded |= entry.state == ALRecoveryEntry::State::Discarded && entry.key == keeper.key(); } - ensure("among the discarded", discarded); + CHECK(discarded); // What a session that ended left of the same item. { @@ -154,14 +162,20 @@ namespace tut left.item = item; left.name = "Card"; left.text = "left behind"; - ensure("left", before.write(left)); + CHECK(before.write(left)); } std::optional offered = keeper.left(); - ensure("offered", offered && offered->text == "left behind"); + REQUIRE(offered); + CHECK(offered->text == "left behind"); text = "left behind"; keeper.took(*offered, 7); - ensure("the window's own written", mine() && mine()->text == "left behind"); - ensure("and the one taken up gone", !keeper.left()); + { + INFO("the window's own written"); + REQUIRE(mine()); + CHECK(mine()->text == "left behind"); + } + UNSCOPED_INFO("and the one taken up gone"); + CHECK_FALSE(keeper.left()); // Turned down: among the discarded, and offered no more. { @@ -173,21 +187,21 @@ namespace tut before.write(left); } offered = keeper.left(); - ensure("offered again", offered && offered->text == "not wanted"); + REQUIRE(offered); + CHECK(offered->text == "not wanted"); keeper.turnDown(*offered); - ensure("turned down: offered no more", !keeper.left()); + UNSCOPED_INFO("turned down: offered no more"); + CHECK_FALSE(keeper.left()); } - template<> template<> - void alrecoverykeeper_object::test<3>() + TEST_CASE_METHOD(alrecoverykeeper_data, "ALRecoveryKeeper: a new item or none moves the key and a failed write is said once", "[ALRecoveryKeeper]") { - set_test_name("another item after a save: the old key forgotten, the new one kept; no item, nothing kept; a write that fails said once"); ALRecoveryKeeper keeper(holder()); keeper.setItem(object, item); unsaved = true; keeper.pump(1, 0.0); keeper.pump(1, ALRecoveryKeeper::DELAY); - ensure("written", mine().has_value()); + REQUIRE(mine().has_value()); const LLUUID was = item; item = LLUUID::generateNewID(); keeper.setItem(object, item); @@ -197,12 +211,15 @@ namespace tut { old_left |= entry.key == ALRecoveryStore::windowKeyOf(object, was); } - ensure("the old key forgotten", !old_left); + UNSCOPED_INFO("the old key forgotten"); + CHECK_FALSE(old_left); keeper.pump(2, 5.0); keeper.pump(2, 5.0 + ALRecoveryKeeper::DELAY); - ensure("the new one kept", mine().has_value()); + UNSCOPED_INFO("the new one kept"); + CHECK(mine().has_value()); keeper.setItem(object, LLUUID::null); - ensure("no item, no key", keeper.key().empty()); + UNSCOPED_INFO("no item, no key"); + CHECK(keeper.key().empty()); // Where the store cannot write: said once. const std::string blocked = folder + "/blocked"; @@ -218,19 +235,19 @@ namespace tut failing.pump(1, ALRecoveryKeeper::DELAY); nowhere.flush(); failing.pump(1, 10.0); - ensure_equals("said", failed, 1); + UNSCOPED_INFO("the failed write said"); + CHECK(failed == 1); failing.pump(2, 11.0); failing.pump(2, 11.0 + ALRecoveryKeeper::DELAY); nowhere.flush(); failing.pump(2, 20.0); - ensure_equals("once", failed, 1); + UNSCOPED_INFO("and not said again"); + CHECK(failed == 1); ALRecovery::useStore(store.get()); } - template<> template<> - void alrecoverykeeper_object::test<4>() + TEST_CASE_METHOD(alrecoverykeeper_data, "ALRecoveryKeeper: the legacy editors' backups are imported as an ended session and removed", "[ALRecoveryKeeper]") { - set_test_name("the legacy editors' backups taken into the store as a session that ended, named as they named them, a notecard's text out of its format, and the files gone"); const std::string temp = folder + "/temp"; std::filesystem::create_directories(fsyspath(temp)); // Written as the editors wrote them, byte for byte. @@ -246,19 +263,25 @@ namespace tut llofstream other(temp + "/unrelated.txt"); other << "left alone"; } - ensure_equals("two taken", ALRecovery::importLegacyBackups(temp), 2); - ensure("the backups gone", !LLFile::isfile(temp + "/Door Script-1A2B3C4D.lslbackup") && !LLFile::isfile(temp + "/Settings-0000ABCD.ncbackup")); - ensure("anything else left alone", LLFile::isfile(temp + "/unrelated.txt")); + CHECK(ALRecovery::importLegacyBackups(temp) == 2); + CHECK_FALSE(LLFile::isfile(temp + "/Door Script-1A2B3C4D.lslbackup")); + CHECK_FALSE(LLFile::isfile(temp + "/Settings-0000ABCD.ncbackup")); + UNSCOPED_INFO("anything else left alone"); + CHECK(LLFile::isfile(temp + "/unrelated.txt")); bool script = false, card = false; for (ALRecoveryEntry& entry : store->left()) { - ensure("whole", store->load(entry)); + CAPTURE(entry.name); + REQUIRE(store->load(entry)); script |= entry.name == "Door Script" && !entry.notecard && entry.text == "default { state_entry() {} }\n" && entry.state == ALRecoveryEntry::State::Unsaved; card |= entry.name == "Settings" && entry.notecard && entry.text == "speed = 2\n"; } - ensure("the script, as left by a session that ended", script); - ensure("the notecard, its text out of its format", card); - ensure_equals("nothing more the next time", ALRecovery::importLegacyBackups(temp), 0); + UNSCOPED_INFO("the script, as left by a session that ended"); + CHECK(script); + UNSCOPED_INFO("the notecard, its text out of its format"); + CHECK(card); + UNSCOPED_INFO("nothing more the next time"); + CHECK(ALRecovery::importLegacyBackups(temp) == 0); } } diff --git a/indra/newview/tests/alsceneexplorerpredicate_test.cpp b/indra/newview/tests/alsceneexplorerpredicate_test.cpp index f8808c3c5d..b7d0fabc86 100644 --- a/indra/newview/tests/alsceneexplorerpredicate_test.cpp +++ b/indra/newview/tests/alsceneexplorerpredicate_test.cpp @@ -14,11 +14,11 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../alsceneexplorerpredicate.h" -namespace tut +namespace { using namespace ALSceneExplorerPredicate; @@ -68,273 +68,312 @@ namespace tut } }; - typedef test_group predicate_group; - typedef predicate_group::object predicate_object; - tut::predicate_group pred("ALSceneExplorerPredicate"); - - // Default constraints match anything (and a null record never does). - template<> template<> - void predicate_object::test<1>() + TEST_CASE_METHOD(predicate_data, "ALSceneExplorerPredicate: default constraints match anything but a null record", "[ALSceneExplorerPredicate]") { - ensure("defaults match", matches(mFacts, mC)); + UNSCOPED_INFO("defaults match"); + CHECK(matches(mFacts, mC)); ItemFacts no_record; - ensure("null record never matches", !matches(no_record, mC)); + UNSCOPED_INFO("null record never matches"); + CHECK_FALSE(matches(no_record, mC)); } // Name search: targeted field only; caller pre-lowercases both sides. - template<> template<> - void predicate_object::test<2>() + TEST_CASE_METHOD(predicate_data, "ALSceneExplorerPredicate: a name search looks in the name and nowhere else", "[ALSceneExplorerPredicate]") { mC.mSearchType = SEARCH_NAME; mC.mFilterSubString = "oak"; - ensure("name substring matches", matches(mFacts, mC)); + UNSCOPED_INFO("name substring matches"); + CHECK(matches(mFacts, mC)); mC.mFilterSubString = "weathered"; // description text - ensure("name search ignores description", !matches(mFacts, mC)); + UNSCOPED_INFO("name search ignores description"); + CHECK_FALSE(matches(mFacts, mC)); } - // Description / UUID / owner searches hit only their own field. - template<> template<> - void predicate_object::test<3>() + TEST_CASE_METHOD(predicate_data, "ALSceneExplorerPredicate: description / UUID / owner searches hit only their own field", "[ALSceneExplorerPredicate]") { mC.mSearchType = SEARCH_DESCRIPTION; mC.mFilterSubString = "weathered"; - ensure("description matches", matches(mFacts, mC)); + UNSCOPED_INFO("description matches"); + CHECK(matches(mFacts, mC)); mC.mFilterSubString = "oak"; - ensure("description search ignores name", !matches(mFacts, mC)); + UNSCOPED_INFO("description search ignores name"); + CHECK_FALSE(matches(mFacts, mC)); mC.mSearchType = SEARCH_UUID; mC.mFilterSubString = mUUID.substr(0, 8); - ensure("uuid prefix matches", matches(mFacts, mC)); + UNSCOPED_INFO("uuid prefix matches"); + CHECK(matches(mFacts, mC)); mC.mFilterSubString = "oak"; - ensure("uuid search ignores name", !matches(mFacts, mC)); + UNSCOPED_INFO("uuid search ignores name"); + CHECK_FALSE(matches(mFacts, mC)); mC.mSearchType = SEARCH_OWNER; mC.mFilterSubString = "rye"; - ensure("owner matches", matches(mFacts, mC)); + UNSCOPED_INFO("owner matches"); + CHECK(matches(mFacts, mC)); mC.mFilterSubString = "oak"; - ensure("owner search ignores name", !matches(mFacts, mC)); + UNSCOPED_INFO("owner search ignores name"); + CHECK_FALSE(matches(mFacts, mC)); } - // ALL searches every field; a miss everywhere fails. - template<> template<> - void predicate_object::test<4>() + TEST_CASE_METHOD(predicate_data, "ALSceneExplorerPredicate: SEARCH_ALL finds text in any field and fails a miss in all", "[ALSceneExplorerPredicate]") { mC.mSearchType = SEARCH_ALL; for (const char* needle : { "oak", "weathered", "rye" }) { mC.mFilterSubString = needle; - ensure(std::string("all-search finds ") + needle, matches(mFacts, mC)); + CAPTURE(needle); + REQUIRE(matches(mFacts, mC)); } mC.mFilterSubString = mUUID.substr(4, 8); - ensure("all-search finds uuid", matches(mFacts, mC)); + UNSCOPED_INFO("all-search finds uuid"); + CHECK(matches(mFacts, mC)); mC.mFilterSubString = "no such text"; - ensure("all-search can fail", !matches(mFacts, mC)); + UNSCOPED_INFO("all-search can fail"); + CHECK_FALSE(matches(mFacts, mC)); } - // Owner modes for an owned object. - template<> template<> - void predicate_object::test<5>() + TEST_CASE_METHOD(predicate_data, "ALSceneExplorerPredicate: mine / others / group owner modes for an owned object", "[ALSceneExplorerPredicate]") { mC.mOwnerMode = OWNER_MINE; - ensure("someone else's object is not mine", !matches(mFacts, mC)); + UNSCOPED_INFO("someone else's object is not mine"); + CHECK_FALSE(matches(mFacts, mC)); mRec.mOwnerId = mAgentId; - ensure("my object is mine", matches(mFacts, mC)); + UNSCOPED_INFO("my object is mine"); + CHECK(matches(mFacts, mC)); mC.mOwnerMode = OWNER_OTHERS; - ensure("my object is not others'", !matches(mFacts, mC)); + UNSCOPED_INFO("my object is not others'"); + CHECK_FALSE(matches(mFacts, mC)); mRec.mOwnerId = mOtherId; - ensure("their object is others'", matches(mFacts, mC)); + UNSCOPED_INFO("their object is others'"); + CHECK(matches(mFacts, mC)); mC.mOwnerMode = OWNER_GROUP; - ensure("user-owned is not group-owned", !matches(mFacts, mC)); + UNSCOPED_INFO("user-owned is not group-owned"); + CHECK_FALSE(matches(mFacts, mC)); mRec.mGroupOwned = true; mRec.mGroupId = mGroupId; - ensure("group-owned passes group mode", matches(mFacts, mC)); + UNSCOPED_INFO("group-owned passes group mode"); + CHECK(matches(mFacts, mC)); mC.mOwnerMode = OWNER_OTHERS; - ensure("group-owned is excluded from others'", !matches(mFacts, mC)); + UNSCOPED_INFO("group-owned is excluded from others'"); + CHECK_FALSE(matches(mFacts, mC)); } - // OWNER_SPECIFIC: direct owner or the deeding group. - template<> template<> - void predicate_object::test<6>() + TEST_CASE_METHOD(predicate_data, "ALSceneExplorerPredicate: OWNER_SPECIFIC matches the direct owner or the deeding group", "[ALSceneExplorerPredicate]") { mC.mOwnerMode = OWNER_SPECIFIC; mC.mOwnerId = mOtherId; - ensure("specific owner matches", matches(mFacts, mC)); + UNSCOPED_INFO("specific owner matches"); + CHECK(matches(mFacts, mC)); mC.mOwnerId = mAgentId; - ensure("specific owner mismatch fails", !matches(mFacts, mC)); + UNSCOPED_INFO("specific owner mismatch fails"); + CHECK_FALSE(matches(mFacts, mC)); mRec.mGroupOwned = true; mRec.mGroupId = mGroupId; mC.mOwnerId = mGroupId; - ensure("specific deeding group matches", matches(mFacts, mC)); + UNSCOPED_INFO("specific deeding group matches"); + CHECK(matches(mFacts, mC)); } // Owner gating: avatars are their own owner; objects without props yet // fail owner modes until the reply lands. - template<> template<> - void predicate_object::test<7>() + TEST_CASE_METHOD(predicate_data, "ALSceneExplorerPredicate: owner modes take avatars as their own owner and wait for props", "[ALSceneExplorerPredicate]") { mFacts.mIsAvatar = true; mFacts.mItemId = mAgentId; mC.mOwnerMode = OWNER_MINE; - ensure("own avatar row is mine", matches(mFacts, mC)); + UNSCOPED_INFO("own avatar row is mine"); + CHECK(matches(mFacts, mC)); mFacts.mItemId = mOtherId; - ensure("other avatar row is not mine", !matches(mFacts, mC)); + UNSCOPED_INFO("other avatar row is not mine"); + CHECK_FALSE(matches(mFacts, mC)); mFacts.mIsAvatar = false; mFacts.mItemId = mRec.mId; mRec.mPropsValid = false; - ensure("unresolved props fail owner modes", !matches(mFacts, mC)); + UNSCOPED_INFO("unresolved props fail owner modes"); + CHECK_FALSE(matches(mFacts, mC)); mRec.mPropsValid = true; mRec.mOwnerId = mAgentId; - ensure("resolved props re-apply owner modes", matches(mFacts, mC)); + UNSCOPED_INFO("resolved props re-apply owner modes"); + CHECK(matches(mFacts, mC)); mRec.mPropsValid = false; mC.mOwnerMode = OWNER_ANY; - ensure("unresolved props pass without an owner mode", matches(mFacts, mC)); + UNSCOPED_INFO("unresolved props pass without an owner mode"); + CHECK(matches(mFacts, mC)); } // Geometry mask: empty = any; otherwise the item's kind must be in it. - template<> template<> - void predicate_object::test<8>() + TEST_CASE_METHOD(predicate_data, "ALSceneExplorerPredicate: a geometry mask passes an item only if its kind is in it", "[ALSceneExplorerPredicate]") { mC.mGeomMask = 1u << ALObjectProperties::GEOM_MESH; - ensure("mesh passes mesh mask", matches(mFacts, mC)); + UNSCOPED_INFO("mesh passes mesh mask"); + CHECK(matches(mFacts, mC)); mC.mGeomMask = 1u << ALObjectProperties::GEOM_PRIM; - ensure("mesh fails prim-only mask", !matches(mFacts, mC)); + UNSCOPED_INFO("mesh fails prim-only mask"); + CHECK_FALSE(matches(mFacts, mC)); mC.mGeomMask = (1u << ALObjectProperties::GEOM_PRIM) | (1u << ALObjectProperties::GEOM_MESH); - ensure("mesh passes prim|mesh mask", matches(mFacts, mC)); + UNSCOPED_INFO("mesh passes prim|mesh mask"); + CHECK(matches(mFacts, mC)); } - // Flag mask requires ALL set bits. - template<> template<> - void predicate_object::test<9>() + TEST_CASE_METHOD(predicate_data, "ALSceneExplorerPredicate: a flag mask requires every bit it sets", "[ALSceneExplorerPredicate]") { mC.mFlagMask = ALObjectProperties::FLAG_SCRIPTED; - ensure("single present flag passes", matches(mFacts, mC)); + UNSCOPED_INFO("single present flag passes"); + CHECK(matches(mFacts, mC)); mC.mFlagMask = ALObjectProperties::FLAG_SCRIPTED | ALObjectProperties::FLAG_LIGHT; - ensure("two present flags pass", matches(mFacts, mC)); + UNSCOPED_INFO("two present flags pass"); + CHECK(matches(mFacts, mC)); mC.mFlagMask = ALObjectProperties::FLAG_SCRIPTED | ALObjectProperties::FLAG_PHANTOM; - ensure("one missing flag fails the AND", !matches(mFacts, mC)); + UNSCOPED_INFO("one missing flag fails the AND"); + CHECK_FALSE(matches(mFacts, mC)); mRec.mFlags |= ALObjectProperties::FLAG_FOR_SALE | ALObjectProperties::FLAG_PAYABLE; mC.mFlagMask = ALObjectProperties::FLAG_FOR_SALE | ALObjectProperties::FLAG_PAYABLE; - ensure("commerce flags filter", matches(mFacts, mC)); + UNSCOPED_INFO("commerce flags filter"); + CHECK(matches(mFacts, mC)); } // Scope: region ignores position; radius compares distance; parcel uses // the caller-resolved containment fact. - template<> template<> - void predicate_object::test<10>() + TEST_CASE_METHOD(predicate_data, "ALSceneExplorerPredicate: region scope ignores distance while radius and parcel scopes filter", "[ALSceneExplorerPredicate]") { mRec.mDistance = 100.f; mC.mScope = SCOPE_REGION; - ensure("region scope ignores distance", matches(mFacts, mC)); + UNSCOPED_INFO("region scope ignores distance"); + CHECK(matches(mFacts, mC)); mC.mScope = SCOPE_RADIUS; mC.mRadius = 64.f; - ensure("beyond radius fails", !matches(mFacts, mC)); + UNSCOPED_INFO("beyond radius fails"); + CHECK_FALSE(matches(mFacts, mC)); mC.mRadius = 128.f; - ensure("within radius passes", matches(mFacts, mC)); + UNSCOPED_INFO("within radius passes"); + CHECK(matches(mFacts, mC)); mC.mScope = SCOPE_PARCEL; mFacts.mInAgentParcel = true; - ensure("inside parcel passes", matches(mFacts, mC)); + UNSCOPED_INFO("inside parcel passes"); + CHECK(matches(mFacts, mC)); mFacts.mInAgentParcel = false; - ensure("outside parcel fails", !matches(mFacts, mC)); + UNSCOPED_INFO("outside parcel fails"); + CHECK_FALSE(matches(mFacts, mC)); } // Heaviness thresholds: roots gated, child prims exempt, zero = off. - template<> template<> - void predicate_object::test<11>() + TEST_CASE_METHOD(predicate_data, "ALSceneExplorerPredicate: land impact and triangle minimums gate roots but not child prims", "[ALSceneExplorerPredicate]") { mC.mMinLandImpact = 50.f; // record has 40 - ensure("under LI threshold fails", !matches(mFacts, mC)); + UNSCOPED_INFO("under LI threshold fails"); + CHECK_FALSE(matches(mFacts, mC)); mC.mMinLandImpact = 40.f; - ensure("at LI threshold passes", matches(mFacts, mC)); + UNSCOPED_INFO("at LI threshold passes"); + CHECK(matches(mFacts, mC)); mC.mMinLandImpact = 0.f; mC.mMinTriangles = 20000; // record has 12000 - ensure("under triangle threshold fails", !matches(mFacts, mC)); + UNSCOPED_INFO("under triangle threshold fails"); + CHECK_FALSE(matches(mFacts, mC)); mC.mMinTriangles = 12000; - ensure("at triangle threshold passes", matches(mFacts, mC)); + UNSCOPED_INFO("at triangle threshold passes"); + CHECK(matches(mFacts, mC)); mC.mMinTriangles = 20000; mFacts.mIsChildPrim = true; - ensure("child prims are exempt from thresholds", matches(mFacts, mC)); + UNSCOPED_INFO("child prims are exempt from thresholds"); + CHECK(matches(mFacts, mC)); + } + + TEST_CASE_METHOD(predicate_data, "ALSceneExplorerPredicate: constraints compose and every one must hold at once", "[ALSceneExplorerPredicate]") + { + mC.mSearchType = SEARCH_NAME; + mC.mFilterSubString = "oak"; + mC.mGeomMask = 1u << ALObjectProperties::GEOM_MESH; + mC.mFlagMask = ALObjectProperties::FLAG_SCRIPTED; + mC.mScope = SCOPE_RADIUS; + mC.mRadius = 64.f; + mC.mMinLandImpact = 10.f; + UNSCOPED_INFO("all constraints satisfied"); + CHECK(matches(mFacts, mC)); + + mC.mFlagMask |= ALObjectProperties::FLAG_PHANTOM; + UNSCOPED_INFO("one failing constraint fails the set"); + CHECK_FALSE(matches(mFacts, mC)); } // Change classification: substring extension/shrink (the typing and // backspacing cases), including the empty-needle transitions. - template<> template<> - void predicate_object::test<13>() + TEST_CASE_METHOD(predicate_data, "ALSceneExplorerPredicate: classifySubstringChange tells typing from backspacing", "[ALSceneExplorerPredicate]") { - ensure_equals("same needle", classifySubstringChange("oak", "oak"), CHANGE_NONE); - ensure_equals("typing from empty", classifySubstringChange("", "oak"), CHANGE_MORE_RESTRICTIVE); - ensure_equals("extending", classifySubstringChange("oak", "oak t"), CHANGE_MORE_RESTRICTIVE); - ensure_equals("backspacing", classifySubstringChange("oak t", "oak"), CHANGE_LESS_RESTRICTIVE); - ensure_equals("clearing", classifySubstringChange("oak", ""), CHANGE_LESS_RESTRICTIVE); - ensure_equals("unrelated text", classifySubstringChange("oak", "pine"), CHANGE_RESTART); + CHECK(classifySubstringChange("oak", "oak") == CHANGE_NONE); + UNSCOPED_INFO("typing from empty"); + CHECK(classifySubstringChange("", "oak") == CHANGE_MORE_RESTRICTIVE); + UNSCOPED_INFO("extending"); + CHECK(classifySubstringChange("oak", "oak t") == CHANGE_MORE_RESTRICTIVE); + UNSCOPED_INFO("backspacing"); + CHECK(classifySubstringChange("oak t", "oak") == CHANGE_LESS_RESTRICTIVE); + UNSCOPED_INFO("clearing"); + CHECK(classifySubstringChange("oak", "") == CHANGE_LESS_RESTRICTIVE); + UNSCOPED_INFO("unrelated text"); + CHECK(classifySubstringChange("oak", "pine") == CHANGE_RESTART); } // Change classification: require-all mask (the feature checkboxes). - template<> template<> - void predicate_object::test<14>() + TEST_CASE_METHOD(predicate_data, "ALSceneExplorerPredicate: classifyRequireAllMaskChange restricts on added bits", "[ALSceneExplorerPredicate]") { const U32 a = ALObjectProperties::FLAG_SCRIPTED; const U32 b = ALObjectProperties::FLAG_LIGHT; const U32 c = ALObjectProperties::FLAG_PHANTOM; - ensure_equals("same mask", classifyRequireAllMaskChange(a | b, a | b), CHANGE_NONE); - ensure_equals("first requirement", classifyRequireAllMaskChange(0, a), CHANGE_MORE_RESTRICTIVE); - ensure_equals("added requirement", classifyRequireAllMaskChange(a, a | b), CHANGE_MORE_RESTRICTIVE); - ensure_equals("removed requirement", classifyRequireAllMaskChange(a | b, a), CHANGE_LESS_RESTRICTIVE); - ensure_equals("last requirement off", classifyRequireAllMaskChange(a, 0), CHANGE_LESS_RESTRICTIVE); - ensure_equals("swapped requirement", classifyRequireAllMaskChange(a | b, a | c), CHANGE_RESTART); + CHECK(classifyRequireAllMaskChange(a | b, a | b) == CHANGE_NONE); + UNSCOPED_INFO("first requirement"); + CHECK(classifyRequireAllMaskChange(0, a) == CHANGE_MORE_RESTRICTIVE); + UNSCOPED_INFO("added requirement"); + CHECK(classifyRequireAllMaskChange(a, a | b) == CHANGE_MORE_RESTRICTIVE); + UNSCOPED_INFO("removed requirement"); + CHECK(classifyRequireAllMaskChange(a | b, a) == CHANGE_LESS_RESTRICTIVE); + UNSCOPED_INFO("last requirement off"); + CHECK(classifyRequireAllMaskChange(a, 0) == CHANGE_LESS_RESTRICTIVE); + UNSCOPED_INFO("swapped requirement"); + CHECK(classifyRequireAllMaskChange(a | b, a | c) == CHANGE_RESTART); } // Change classification: allowed-set mask (the type checkboxes; 0 = any). - template<> template<> - void predicate_object::test<15>() + TEST_CASE_METHOD(predicate_data, "ALSceneExplorerPredicate: classifyAllowedMaskChange relaxes on added kinds with 0 as any", "[ALSceneExplorerPredicate]") { const U32 prim = 1u << ALObjectProperties::GEOM_PRIM; const U32 mesh = 1u << ALObjectProperties::GEOM_MESH; - ensure_equals("same set", classifyAllowedMaskChange(prim, prim), CHANGE_NONE); - ensure_equals("any to one kind", classifyAllowedMaskChange(0, prim), CHANGE_MORE_RESTRICTIVE); - ensure_equals("one kind to any", classifyAllowedMaskChange(prim, 0), CHANGE_LESS_RESTRICTIVE); - ensure_equals("allowing another kind", classifyAllowedMaskChange(prim, prim | mesh), CHANGE_LESS_RESTRICTIVE); - ensure_equals("disallowing a kind", classifyAllowedMaskChange(prim | mesh, prim), CHANGE_MORE_RESTRICTIVE); - ensure_equals("swapped kind", classifyAllowedMaskChange(prim, mesh), CHANGE_RESTART); + CHECK(classifyAllowedMaskChange(prim, prim) == CHANGE_NONE); + UNSCOPED_INFO("any to one kind"); + CHECK(classifyAllowedMaskChange(0, prim) == CHANGE_MORE_RESTRICTIVE); + UNSCOPED_INFO("one kind to any"); + CHECK(classifyAllowedMaskChange(prim, 0) == CHANGE_LESS_RESTRICTIVE); + UNSCOPED_INFO("allowing another kind"); + CHECK(classifyAllowedMaskChange(prim, prim | mesh) == CHANGE_LESS_RESTRICTIVE); + UNSCOPED_INFO("disallowing a kind"); + CHECK(classifyAllowedMaskChange(prim | mesh, prim) == CHANGE_MORE_RESTRICTIVE); + UNSCOPED_INFO("swapped kind"); + CHECK(classifyAllowedMaskChange(prim, mesh) == CHANGE_RESTART); } // Change classification: minimum thresholds (0 = off). - template<> template<> - void predicate_object::test<16>() + TEST_CASE_METHOD(predicate_data, "ALSceneExplorerPredicate: classifyMinThresholdChange restricts on a raise with 0 as off", "[ALSceneExplorerPredicate]") { - ensure_equals("same threshold", classifyMinThresholdChange(50.f, 50.f), CHANGE_NONE); - ensure_equals("enabling", classifyMinThresholdChange(0.f, 50.f), CHANGE_MORE_RESTRICTIVE); - ensure_equals("raising", classifyMinThresholdChange(50.f, 100.f), CHANGE_MORE_RESTRICTIVE); - ensure_equals("lowering", classifyMinThresholdChange(100.f, 50.f), CHANGE_LESS_RESTRICTIVE); - ensure_equals("disabling", classifyMinThresholdChange(50.f, 0.f), CHANGE_LESS_RESTRICTIVE); - } - - // Predicates compose: every constraint must hold at once. - template<> template<> - void predicate_object::test<12>() - { - mC.mSearchType = SEARCH_NAME; - mC.mFilterSubString = "oak"; - mC.mGeomMask = 1u << ALObjectProperties::GEOM_MESH; - mC.mFlagMask = ALObjectProperties::FLAG_SCRIPTED; - mC.mScope = SCOPE_RADIUS; - mC.mRadius = 64.f; - mC.mMinLandImpact = 10.f; - ensure("all constraints satisfied", matches(mFacts, mC)); - - mC.mFlagMask |= ALObjectProperties::FLAG_PHANTOM; - ensure("one failing constraint fails the set", !matches(mFacts, mC)); + CHECK(classifyMinThresholdChange(50.f, 50.f) == CHANGE_NONE); + UNSCOPED_INFO("enabling"); + CHECK(classifyMinThresholdChange(0.f, 50.f) == CHANGE_MORE_RESTRICTIVE); + UNSCOPED_INFO("raising"); + CHECK(classifyMinThresholdChange(50.f, 100.f) == CHANGE_MORE_RESTRICTIVE); + UNSCOPED_INFO("lowering"); + CHECK(classifyMinThresholdChange(100.f, 50.f) == CHANGE_LESS_RESTRICTIVE); + UNSCOPED_INFO("disabling"); + CHECK(classifyMinThresholdChange(50.f, 0.f) == CHANGE_LESS_RESTRICTIVE); } } diff --git a/indra/newview/tests/alscopedata_test.cpp b/indra/newview/tests/alscopedata_test.cpp index 53d5d9c174..523b9af84c 100644 --- a/indra/newview/tests/alscopedata_test.cpp +++ b/indra/newview/tests/alscopedata_test.cpp @@ -14,13 +14,14 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include +#include "altestmatchers.h" #include "../alscopedata.h" #include -namespace tut +namespace { struct scope_data { @@ -100,55 +101,48 @@ namespace tut } }; - typedef test_group scope_group; - typedef scope_group::object scope_object; - tut::scope_group sg("ALScopeData"); - // A fresh object measures nothing and reports nothing. - template<> template<> - void scope_object::test<1>() + TEST_CASE_METHOD(scope_data, "ALScopeData: a fresh object measures nothing and reports nothing", "[ALScopeData]") { - ensure("starts empty", mScope.isEmpty()); - ensure_equals("no samples", mScope.getSampleCount(), 0); - ensure_approximately_equals("no peak", mScope.getPeak(ALScopeData::CH_LUMA), 0.f, 6); - ensure_approximately_equals("no bins", binSum(mScope, ALScopeData::CH_RED), 0.f, 6); + CHECK(mScope.isEmpty()); + CHECK(mScope.getSampleCount() == 0); + CHECK_THAT(mScope.getPeak(ALScopeData::CH_LUMA), ll_test::WithinBits(0.f, 6)); + CHECK_THAT(binSum(mScope, ALScopeData::CH_RED), ll_test::WithinBits(0.f, 6)); } // A flat image puts everything in one bin per channel. - template<> template<> - void scope_object::test<2>() + TEST_CASE_METHOD(scope_data, "ALScopeData: a flat image puts everything in one bin per channel", "[ALScopeData]") { const std::vector px = flat(64, 128, 192, 100); mScope.accumulate(px.data(), 100, 1); - ensure_equals("sample count", mScope.getSampleCount(), 100); - ensure_approximately_equals("all red in bin 64", mScope.getBin(ALScopeData::CH_RED, 64), 1.f, 5); - ensure_approximately_equals("all green in bin 128", mScope.getBin(ALScopeData::CH_GREEN, 128), 1.f, 5); - ensure_approximately_equals("all blue in bin 192", mScope.getBin(ALScopeData::CH_BLUE, 192), 1.f, 5); - ensure_approximately_equals("nothing anywhere else", mScope.getBin(ALScopeData::CH_RED, 65), 0.f, 6); + CHECK(mScope.getSampleCount() == 100); + CHECK_THAT(mScope.getBin(ALScopeData::CH_RED, 64), ll_test::WithinBits(1.f, 5)); + CHECK_THAT(mScope.getBin(ALScopeData::CH_GREEN, 128), ll_test::WithinBits(1.f, 5)); + CHECK_THAT(mScope.getBin(ALScopeData::CH_BLUE, 192), ll_test::WithinBits(1.f, 5)); + UNSCOPED_INFO("nothing anywhere else"); + CHECK_THAT(mScope.getBin(ALScopeData::CH_RED, 65), ll_test::WithinBits(0.f, 6)); } // Bins are shares of the sample, so they sum to one whatever the size. - template<> template<> - void scope_object::test<3>() + TEST_CASE_METHOD(scope_data, "ALScopeData: bins are shares of the sample and sum to one whatever its size", "[ALScopeData]") { const std::vector px = ramp(); mScope.accumulate(px.data(), ALScopeData::BIN_COUNT, 1); for (S32 c = 0; c < ALScopeData::CH_COUNT; ++c) { - ensure_approximately_equals("bins sum to 1", - binSum(mScope, (ALScopeData::EChannel)c), 1.f, 4); + CAPTURE(c); + REQUIRE_THAT(binSum(mScope, (ALScopeData::EChannel)c), ll_test::WithinBits(1.f, 4)); } - ensure_approximately_equals("a flat ramp peaks at 1/256", - mScope.getPeak(ALScopeData::CH_RED), - 1.f / (F32)ALScopeData::BIN_COUNT, 5); + UNSCOPED_INFO("a flat ramp peaks at 1/256"); + CHECK_THAT(mScope.getPeak(ALScopeData::CH_RED), + ll_test::WithinBits(1.f / (F32)ALScopeData::BIN_COUNT, 5)); } // Two samples of different sizes but the same content compare equal, // which is the property that lets the display ignore the sample size. - template<> template<> - void scope_object::test<4>() + TEST_CASE_METHOD(scope_data, "ALScopeData: samples of different sizes but the same content compare equal", "[ALScopeData]") { ALScopeData small_sample; ALScopeData large_sample; @@ -159,16 +153,15 @@ namespace tut for (S32 i = 0; i < ALScopeData::BIN_COUNT; ++i) { - ensure_approximately_equals("size independent", - small_sample.getBin(ALScopeData::CH_RED, i), - large_sample.getBin(ALScopeData::CH_RED, i), 5); + CAPTURE(i); + REQUIRE_THAT(small_sample.getBin(ALScopeData::CH_RED, i), + ll_test::WithinBits(large_sample.getBin(ALScopeData::CH_RED, i), 5)); } } // Luma uses Rec.709 on the encoded values. Pure green must land far above // pure blue; equal grey must land on itself. - template<> template<> - void scope_object::test<5>() + TEST_CASE_METHOD(scope_data, "ALScopeData: luma uses Rec.709 on the encoded values", "[ALScopeData]") { ALScopeData green; ALScopeData blue; @@ -181,15 +174,15 @@ namespace tut grey.accumulate(n.data(), 16, 1); // 0.7152 * 255 = 182.4; 0.0722 * 255 = 18.4 - ensure_approximately_equals("green luma", green.getBin(ALScopeData::CH_LUMA, 182), 1.f, 5); - ensure_approximately_equals("blue luma", blue.getBin(ALScopeData::CH_LUMA, 18), 1.f, 5); - ensure_approximately_equals("grey is its own luma", grey.getBin(ALScopeData::CH_LUMA, 137), 1.f, 5); + CHECK_THAT(green.getBin(ALScopeData::CH_LUMA, 182), ll_test::WithinBits(1.f, 5)); + CHECK_THAT(blue.getBin(ALScopeData::CH_LUMA, 18), ll_test::WithinBits(1.f, 5)); + UNSCOPED_INFO("grey is its own luma"); + CHECK_THAT(grey.getBin(ALScopeData::CH_LUMA, 137), ll_test::WithinBits(1.f, 5)); } // The weights sum to unity in fixed point, so white cannot overflow the // last bin and black cannot underflow the first. - template<> template<> - void scope_object::test<6>() + TEST_CASE_METHOD(scope_data, "ALScopeData: white lands in the last luma bin and black in the first", "[ALScopeData]") { ALScopeData white; ALScopeData black; @@ -198,15 +191,13 @@ namespace tut white.accumulate(w.data(), 4, 1); black.accumulate(k.data(), 4, 1); - ensure_approximately_equals("white luma is 255", - white.getBin(ALScopeData::CH_LUMA, ALScopeData::BIN_COUNT - 1), 1.f, 5); - ensure_approximately_equals("black luma is 0", - black.getBin(ALScopeData::CH_LUMA, 0), 1.f, 5); + UNSCOPED_INFO("white luma is 255"); + CHECK_THAT(white.getBin(ALScopeData::CH_LUMA, ALScopeData::BIN_COUNT - 1), ll_test::WithinBits(1.f, 5)); + CHECK_THAT(black.getBin(ALScopeData::CH_LUMA, 0), ll_test::WithinBits(1.f, 5)); } // Clipping readouts are the extreme bins, reported as a share of sample. - template<> template<> - void scope_object::test<7>() + TEST_CASE_METHOD(scope_data, "ALScopeData: clipping readouts are the shares in the extreme bins", "[ALScopeData]") { std::vector px; // 10 blown, 30 crushed, 60 mid. @@ -215,80 +206,79 @@ namespace tut for (S32 i = 0; i < 60; ++i) { px.insert(px.end(), { 128, 128, 128, 255 }); } mScope.accumulate(px.data(), 100, 1); - ensure_approximately_equals("10% blown", - mScope.getClippedHigh(ALScopeData::CH_RED), 0.10f, 4); - ensure_approximately_equals("30% crushed", - mScope.getClippedLow(ALScopeData::CH_RED), 0.30f, 4); - ensure_approximately_equals("luma agrees on the blown share", - mScope.getClippedHigh(ALScopeData::CH_LUMA), 0.10f, 4); + CHECK_THAT(mScope.getClippedHigh(ALScopeData::CH_RED), ll_test::WithinBits(0.10f, 4)); + CHECK_THAT(mScope.getClippedLow(ALScopeData::CH_RED), ll_test::WithinBits(0.30f, 4)); + UNSCOPED_INFO("luma agrees on the blown share"); + CHECK_THAT(mScope.getClippedHigh(ALScopeData::CH_LUMA), ll_test::WithinBits(0.10f, 4)); } // A single blown pixel in a large sample still registers. This is the // case a box-filtered downsample would have averaged away, and the reason // the capture point-samples. - template<> template<> - void scope_object::test<8>() + TEST_CASE_METHOD(scope_data, "ALScopeData: a single blown pixel in a large sample still registers", "[ALScopeData]") { std::vector px = flat(100, 100, 100, 10000); px[0] = px[1] = px[2] = 255; mScope.accumulate(px.data(), 10000, 1); - ensure("one blown pixel is visible", mScope.getClippedHigh(ALScopeData::CH_RED) > 0.f); - ensure_approximately_equals("and it is 1 in 10000", - mScope.getClippedHigh(ALScopeData::CH_RED), 0.0001f, 6); + CHECK(mScope.getClippedHigh(ALScopeData::CH_RED) > 0.f); + CHECK_THAT(mScope.getClippedHigh(ALScopeData::CH_RED), ll_test::WithinBits(0.0001f, 6)); } // Accumulating replaces rather than adding to the previous measurement. - template<> template<> - void scope_object::test<9>() + TEST_CASE_METHOD(scope_data, "ALScopeData: accumulating replaces rather than adds to the previous measurement", "[ALScopeData]") { const std::vector a = flat(10, 10, 10, 50); const std::vector b = flat(200, 200, 200, 50); mScope.accumulate(a.data(), 50, 1); mScope.accumulate(b.data(), 50, 1); - ensure_approximately_equals("old bin cleared", mScope.getBin(ALScopeData::CH_RED, 10), 0.f, 6); - ensure_approximately_equals("new bin full", mScope.getBin(ALScopeData::CH_RED, 200), 1.f, 5); - ensure_equals("count is the latest", mScope.getSampleCount(), 50); + UNSCOPED_INFO("old bin cleared"); + CHECK_THAT(mScope.getBin(ALScopeData::CH_RED, 10), ll_test::WithinBits(0.f, 6)); + UNSCOPED_INFO("new bin full"); + CHECK_THAT(mScope.getBin(ALScopeData::CH_RED, 200), ll_test::WithinBits(1.f, 5)); + UNSCOPED_INFO("count is the latest"); + CHECK(mScope.getSampleCount() == 50); } // Degenerate input clears rather than reading past the buffer. - template<> template<> - void scope_object::test<10>() + TEST_CASE_METHOD(scope_data, "ALScopeData: degenerate input clears rather than reading past the buffer", "[ALScopeData]") { const std::vector px = flat(60, 60, 60, 8); mScope.accumulate(px.data(), 8, 1); - ensure("measured", !mScope.isEmpty()); + CHECK_FALSE(mScope.isEmpty()); mScope.accumulate(px.data(), 0, 1); - ensure("zero count clears", mScope.isEmpty()); + UNSCOPED_INFO("zero count clears"); + CHECK(mScope.isEmpty()); mScope.accumulate(px.data(), 8, 1); mScope.accumulate(nullptr, 8, 1); - ensure("null clears", mScope.isEmpty()); + UNSCOPED_INFO("null clears"); + CHECK(mScope.isEmpty()); mScope.accumulate(px.data(), 8, 1); mScope.accumulate(px.data(), -5, 1); - ensure("negative count clears", mScope.isEmpty()); + UNSCOPED_INFO("negative count clears"); + CHECK(mScope.isEmpty()); } // The first blend takes the new sample whole; fading up from zero would // read as the scope being broken for the first few updates. - template<> template<> - void scope_object::test<11>() + TEST_CASE_METHOD(scope_data, "ALScopeData: the first blend takes the new sample whole", "[ALScopeData]") { ALScopeData fresh; const std::vector px = flat(90, 90, 90, 20); fresh.accumulate(px.data(), 20, 1); mScope.blendToward(fresh, 0.25f); - ensure_approximately_equals("taken whole", mScope.getBin(ALScopeData::CH_RED, 90), 1.f, 5); - ensure_equals("count carried", mScope.getSampleCount(), 20); + CHECK_THAT(mScope.getBin(ALScopeData::CH_RED, 90), ll_test::WithinBits(1.f, 5)); + UNSCOPED_INFO("count carried"); + CHECK(mScope.getSampleCount() == 20); } // A later blend moves partway and converges on repetition. - template<> template<> - void scope_object::test<12>() + TEST_CASE_METHOD(scope_data, "ALScopeData: a later blend moves partway and converges on repetition", "[ALScopeData]") { ALScopeData first; ALScopeData second; @@ -299,24 +289,23 @@ namespace tut mScope.blendToward(first, 1.f); mScope.blendToward(second, 0.5f); - ensure_approximately_equals("halfway out of the old bin", - mScope.getBin(ALScopeData::CH_RED, 40), 0.5f, 4); - ensure_approximately_equals("halfway into the new one", - mScope.getBin(ALScopeData::CH_RED, 200), 0.5f, 4); + UNSCOPED_INFO("halfway out of the old bin"); + CHECK_THAT(mScope.getBin(ALScopeData::CH_RED, 40), ll_test::WithinBits(0.5f, 4)); + UNSCOPED_INFO("halfway into the new one"); + CHECK_THAT(mScope.getBin(ALScopeData::CH_RED, 200), ll_test::WithinBits(0.5f, 4)); for (S32 i = 0; i < 40; ++i) { mScope.blendToward(second, 0.5f); } - ensure_approximately_equals("converges on the new sample", - mScope.getBin(ALScopeData::CH_RED, 200), 1.f, 4); - ensure_approximately_equals("and leaves the old", - mScope.getBin(ALScopeData::CH_RED, 40), 0.f, 4); + UNSCOPED_INFO("converges on the new sample"); + CHECK_THAT(mScope.getBin(ALScopeData::CH_RED, 200), ll_test::WithinBits(1.f, 4)); + UNSCOPED_INFO("and leaves the old"); + CHECK_THAT(mScope.getBin(ALScopeData::CH_RED, 40), ll_test::WithinBits(0.f, 4)); } // Blending never leaves the peak below a bin it will be asked to scale. - template<> template<> - void scope_object::test<13>() + TEST_CASE_METHOD(scope_data, "ALScopeData: blending never leaves the peak below a bin it will be asked to scale", "[ALScopeData]") { ALScopeData first; ALScopeData second; @@ -334,46 +323,41 @@ namespace tut const F32 peak = mScope.getPeak(ch); for (S32 i = 0; i < ALScopeData::BIN_COUNT; ++i) { - ensure("no bin exceeds the peak", mScope.getBin(ch, i) <= peak + 1e-6f); + CAPTURE(c, i); + REQUIRE(mScope.getBin(ch, i) <= peak + 1e-6f); } } } // Blending toward nothing keeps what is already measured, so closing and // reopening the source does not wipe the display. - template<> template<> - void scope_object::test<14>() + TEST_CASE_METHOD(scope_data, "ALScopeData: blending toward nothing keeps what is already measured", "[ALScopeData]") { const std::vector px = flat(150, 150, 150, 32); mScope.accumulate(px.data(), 32, 1); const ALScopeData nothing; mScope.blendToward(nothing, 0.5f); - ensure_approximately_equals("unchanged", mScope.getBin(ALScopeData::CH_RED, 150), 1.f, 5); + CHECK_THAT(mScope.getBin(ALScopeData::CH_RED, 150), ll_test::WithinBits(1.f, 5)); } // Out-of-range queries are answered, not asserted on. - template<> template<> - void scope_object::test<15>() + TEST_CASE_METHOD(scope_data, "ALScopeData: out-of-range bin and peak queries answer zero", "[ALScopeData]") { const std::vector px = flat(1, 2, 3, 4); mScope.accumulate(px.data(), 4, 1); - ensure_approximately_equals("negative bin", mScope.getBin(ALScopeData::CH_RED, -1), 0.f, 6); - ensure_approximately_equals("past the end", - mScope.getBin(ALScopeData::CH_RED, ALScopeData::BIN_COUNT), 0.f, 6); - ensure_approximately_equals("bad channel", - mScope.getBin((ALScopeData::EChannel)99, 0), 0.f, 6); - ensure_approximately_equals("bad channel peak", - mScope.getPeak((ALScopeData::EChannel)-3), 0.f, 6); + CHECK_THAT(mScope.getBin(ALScopeData::CH_RED, -1), ll_test::WithinBits(0.f, 6)); + CHECK_THAT(mScope.getBin(ALScopeData::CH_RED, ALScopeData::BIN_COUNT), ll_test::WithinBits(0.f, 6)); + CHECK_THAT(mScope.getBin((ALScopeData::EChannel)99, 0), ll_test::WithinBits(0.f, 6)); + CHECK_THAT(mScope.getPeak((ALScopeData::EChannel)-3), ll_test::WithinBits(0.f, 6)); } // --- vectorscope --------------------------------------------------------- // Grey has no chroma, so every neutral sample lands in the middle. That is // the reading a colourist checks first: a trace off-centre at the origin // means a cast. - template<> template<> - void scope_object::test<16>() + TEST_CASE_METHOD(scope_data, "ALScopeData: grey has no chroma so every neutral sample lands in the middle", "[ALScopeData]") { for (U8 level : { (U8)0, (U8)64, (U8)128, (U8)200, (U8)255 }) { @@ -382,105 +366,109 @@ namespace tut S32 u = -1, v = -1; soleCell(mScope, u, v); // Two central cells straddle zero on an even grid; either is right. - ensure("grey sits at the centre in u", u == ALScopeData::CHROMA_SIZE / 2 || - u == ALScopeData::CHROMA_SIZE / 2 - 1); - ensure("grey sits at the centre in v", v == ALScopeData::CHROMA_SIZE / 2 || - v == ALScopeData::CHROMA_SIZE / 2 - 1); + UNSCOPED_INFO("grey sits at the centre in u"); + REQUIRE((u == ALScopeData::CHROMA_SIZE / 2 || + u == ALScopeData::CHROMA_SIZE / 2 - 1)); + UNSCOPED_INFO("grey sits at the centre in v"); + REQUIRE((v == ALScopeData::CHROMA_SIZE / 2 || + v == ALScopeData::CHROMA_SIZE / 2 - 1)); } } // The primaries land in the directions the wheels put them, so pushing a // wheel and watching the trace agree is meaningful. Red at angle 0 means // right of centre; green and blue at 120 and 240 degrees. - template<> template<> - void scope_object::test<17>() + TEST_CASE_METHOD(scope_data, "ALScopeData: the primaries land in the directions the wheels put them", "[ALScopeData]") { const S32 mid = ALScopeData::CHROMA_SIZE / 2; mScope.accumulate(flat(255, 0, 0, 8).data(), 8, 1); S32 u = -1, v = -1; soleCell(mScope, u, v); - ensure("red is right of centre", u > mid); - ensure("and level with it", v == mid || v == mid - 1); + UNSCOPED_INFO("red is right of centre"); + CHECK(u > mid); + UNSCOPED_INFO("and level with it"); + CHECK((v == mid || v == mid - 1)); mScope.accumulate(flat(0, 255, 0, 8).data(), 8, 1); soleCell(mScope, u, v); - ensure("green is left of centre", u < mid); - ensure("and above it", v > mid); + UNSCOPED_INFO("green is left of centre"); + CHECK(u < mid); + UNSCOPED_INFO("and above it"); + CHECK(v > mid); mScope.accumulate(flat(0, 0, 255, 8).data(), 8, 1); soleCell(mScope, u, v); - ensure("blue is left of centre", u < mid); - ensure("and below it", v < mid); + UNSCOPED_INFO("blue is left of centre"); + CHECK(u < mid); + UNSCOPED_INFO("and below it"); + CHECK(v < mid); } // Cells are shares of the sample, like the histogram's bins, so they sum // to one and blend the same way. - template<> template<> - void scope_object::test<18>() + TEST_CASE_METHOD(scope_data, "ALScopeData: chroma cells are shares that sum to one and blend like bins", "[ALScopeData]") { mScope.accumulate(ramp().data(), ALScopeData::BIN_COUNT, 1); - ensure_approximately_equals("cells sum to one", chromaSum(mScope), 1.f, 4); - ensure("peak is a real share", mScope.getChromaPeak() > 0.f && - mScope.getChromaPeak() <= 1.f); + CHECK_THAT(chromaSum(mScope), ll_test::WithinBits(1.f, 4)); + CHECK(mScope.getChromaPeak() > 0.f); + CHECK(mScope.getChromaPeak() <= 1.f); // A ramp is entirely neutral, so all of it is in one place. - ensure_approximately_equals("a grey ramp is a point", mScope.getChromaPeak(), 1.f, 4); + UNSCOPED_INFO("a grey ramp is a point"); + CHECK_THAT(mScope.getChromaPeak(), ll_test::WithinBits(1.f, 4)); ALScopeData red; red.accumulate(flat(255, 0, 0, 8).data(), 8, 1); mScope.blendToward(red, 0.5f); - ensure_approximately_equals("still sums to one after a blend", - chromaSum(mScope), 1.f, 4); - ensure_approximately_equals("and the peak matches the cells", - mScope.getChromaPeak(), 0.5f, 4); + UNSCOPED_INFO("still sums to one after a blend"); + CHECK_THAT(chromaSum(mScope), ll_test::WithinBits(1.f, 4)); + UNSCOPED_INFO("and the peak matches the cells"); + CHECK_THAT(mScope.getChromaPeak(), ll_test::WithinBits(0.5f, 4)); } // Out-of-range cells answer zero rather than assert, and clear() empties // the grid along with everything else. - template<> template<> - void scope_object::test<19>() + TEST_CASE_METHOD(scope_data, "ALScopeData: out-of-range cells answer zero and clear empties the grid", "[ALScopeData]") { mScope.accumulate(flat(200, 30, 30, 4).data(), 4, 1); - ensure("something was measured", mScope.getChromaPeak() > 0.f); + CHECK(mScope.getChromaPeak() > 0.f); - ensure_approximately_equals("negative u", mScope.getChromaCell(-1, 0), 0.f, 6); - ensure_approximately_equals("negative v", mScope.getChromaCell(0, -1), 0.f, 6); - ensure_approximately_equals("past the end in u", - mScope.getChromaCell(ALScopeData::CHROMA_SIZE, 0), 0.f, 6); - ensure_approximately_equals("past the end in v", - mScope.getChromaCell(0, ALScopeData::CHROMA_SIZE), 0.f, 6); + CHECK_THAT(mScope.getChromaCell(-1, 0), ll_test::WithinBits(0.f, 6)); + CHECK_THAT(mScope.getChromaCell(0, -1), ll_test::WithinBits(0.f, 6)); + CHECK_THAT(mScope.getChromaCell(ALScopeData::CHROMA_SIZE, 0), ll_test::WithinBits(0.f, 6)); + CHECK_THAT(mScope.getChromaCell(0, ALScopeData::CHROMA_SIZE), ll_test::WithinBits(0.f, 6)); mScope.clear(); - ensure_approximately_equals("cleared peak", mScope.getChromaPeak(), 0.f, 6); - ensure_approximately_equals("cleared cells", chromaSum(mScope), 0.f, 6); + CHECK_THAT(mScope.getChromaPeak(), ll_test::WithinBits(0.f, 6)); + CHECK_THAT(chromaSum(mScope), ll_test::WithinBits(0.f, 6)); } // Cell centres run -1 to 1 in units of MAX_CHROMA, and the binning is the // inverse of that mapping -- so a plot laid out from chromaCellCentre puts // the trace where accumulate() put it, not half a cell away. - template<> template<> - void scope_object::test<20>() + TEST_CASE_METHOD(scope_data, "ALScopeData: chroma cell centres rise from -1 to 1 and straddle zero", "[ALScopeData]") { const S32 last = ALScopeData::CHROMA_SIZE - 1; - ensure("first cell is at the low edge", ALScopeData::chromaCellCentre(0) < -0.9f); - ensure("last cell is at the high edge", ALScopeData::chromaCellCentre(last) > 0.9f); - ensure_approximately_equals("the grid straddles zero", - ALScopeData::chromaCellCentre(ALScopeData::CHROMA_SIZE / 2) + + UNSCOPED_INFO("first cell is at the low edge"); + CHECK(ALScopeData::chromaCellCentre(0) < -0.9f); + UNSCOPED_INFO("last cell is at the high edge"); + CHECK(ALScopeData::chromaCellCentre(last) > 0.9f); + UNSCOPED_INFO("the grid straddles zero"); + CHECK_THAT(ALScopeData::chromaCellCentre(ALScopeData::CHROMA_SIZE / 2) + ALScopeData::chromaCellCentre(ALScopeData::CHROMA_SIZE / 2 - 1), - 0.f, 5); + ll_test::WithinBits(0.f, 5)); for (S32 i = 1; i < ALScopeData::CHROMA_SIZE; ++i) { - ensure("centres increase", - ALScopeData::chromaCellCentre(i) > ALScopeData::chromaCellCentre(i - 1)); + CAPTURE(i); + REQUIRE(ALScopeData::chromaCellCentre(i) > ALScopeData::chromaCellCentre(i - 1)); } } // The whole reason the waveform exists: a histogram cannot tell a blown sky // from a blown face, and this must. Left half black, right half white -- // they have to land in different columns, at opposite ends of the scale. - template<> template<> - void scope_object::test<21>() + TEST_CASE_METHOD(scope_data, "ALScopeData: the waveform puts black and white halves in different columns", "[ALScopeData]") { const S32 W = 64, H = 8; std::vector px((size_t)W * H * 4, 255); @@ -500,21 +488,20 @@ namespace tut const S32 left = ALScopeData::WAVE_COLUMNS / 4; // inside the black half const S32 right = 3 * ALScopeData::WAVE_COLUMNS / 4; // inside the white half - ensure_approximately_equals("black column is entirely at the bottom", - mScope.getWaveCell(ALScopeData::CH_LUMA, left, 0), 1.f, 5); - ensure_equals("black column has nothing at the top", - mScope.getWaveCell(ALScopeData::CH_LUMA, left, top), 0.f); - ensure_approximately_equals("white column is entirely at the top", - mScope.getWaveCell(ALScopeData::CH_LUMA, right, top), 1.f, 5); - ensure_equals("white column has nothing at the bottom", - mScope.getWaveCell(ALScopeData::CH_LUMA, right, 0), 0.f); + UNSCOPED_INFO("black column is entirely at the bottom"); + CHECK_THAT(mScope.getWaveCell(ALScopeData::CH_LUMA, left, 0), ll_test::WithinBits(1.f, 5)); + UNSCOPED_INFO("black column has nothing at the top"); + CHECK(mScope.getWaveCell(ALScopeData::CH_LUMA, left, top) == 0.f); + UNSCOPED_INFO("white column is entirely at the top"); + CHECK_THAT(mScope.getWaveCell(ALScopeData::CH_LUMA, right, top), ll_test::WithinBits(1.f, 5)); + UNSCOPED_INFO("white column has nothing at the bottom"); + CHECK(mScope.getWaveCell(ALScopeData::CH_LUMA, right, 0) == 0.f); } // A left-to-right ramp is the shape everyone recognises: the trace climbs // across the frame. If columns and levels were ever transposed, or the // column mapping inverted, this is what catches it. - template<> template<> - void scope_object::test<22>() + TEST_CASE_METHOD(scope_data, "ALScopeData: a left-to-right ramp draws a trace that climbs across the frame", "[ALScopeData]") { const S32 W = ALScopeData::WAVE_COLUMNS, H = 4; std::vector px((size_t)W * H * 4, 255); @@ -540,22 +527,25 @@ namespace tut { if (mScope.getWaveCell(ALScopeData::CH_LUMA, column, level) > 0.f) { - ensure_equals("only one level per column", lit, -1); + UNSCOPED_INFO("only one level per column"); + REQUIRE(lit == -1); lit = level; } } - ensure("every column is lit somewhere", lit >= 0); - ensure("the trace never descends", lit >= previous); + UNSCOPED_INFO("every column is lit somewhere"); + REQUIRE(lit >= 0); + UNSCOPED_INFO("the trace never descends"); + REQUIRE(lit >= previous); previous = lit; } - ensure("and it does climb", previous > 0); + UNSCOPED_INFO("and it does climb"); + CHECK(previous > 0); } // Shares are per column, not per sample -- that is what lets the plot's // intensity mean the same thing whatever the sample's width was. So a flat // frame reads 1.0 in every column, not 1/WAVE_COLUMNS. - template<> template<> - void scope_object::test<23>() + TEST_CASE_METHOD(scope_data, "ALScopeData: waveform shares are per column so a flat frame reads 1 in each", "[ALScopeData]") { auto column_total = [&](S32 column) { @@ -575,11 +565,11 @@ namespace tut mScope.accumulate(flat(128, 128, 128, W * H).data(), W, H); for (S32 column = 0; column < ALScopeData::WAVE_COLUMNS; ++column) { - ensure_approximately_equals("every column holds one column's worth", - column_total(column), 1.f, 5); + UNSCOPED_INFO("every column holds one column's worth"); + REQUIRE_THAT(column_total(column), ll_test::WithinBits(1.f, 5)); } - ensure_approximately_equals("peak is a full column", - mScope.getWavePeak(ALScopeData::CH_LUMA), 1.f, 5); + UNSCOPED_INFO("peak is a full column"); + CHECK_THAT(mScope.getWavePeak(ALScopeData::CH_LUMA), ll_test::WithinBits(1.f, 5)); // Narrower than the grid: some columns have no pixels behind them at // all. Those stay empty rather than dividing by zero, and the ones that @@ -593,16 +583,17 @@ namespace tut if (total > 0.f) { ++filled; - ensure_approximately_equals("a filled column is still whole", total, 1.f, 5); + UNSCOPED_INFO("a filled column is still whole"); + REQUIRE_THAT(total, ll_test::WithinBits(1.f, 5)); } } - ensure_equals("one filled column per source column", filled, NARROW); + UNSCOPED_INFO("one filled column per source column"); + CHECK(filled == NARROW); } // Channels are measured separately, which is the whole point of a parade: a // cast shows as the three traces sitting at different heights. - template<> template<> - void scope_object::test<24>() + TEST_CASE_METHOD(scope_data, "ALScopeData: each channel lands at its own level in the parade", "[ALScopeData]") { const S32 W = 32, H = 4; mScope.accumulate(flat(255, 128, 0, W * H).data(), W, H); @@ -610,38 +601,31 @@ namespace tut const S32 column = ALScopeData::WAVE_COLUMNS / 2; const S32 top = ALScopeData::WAVE_LEVELS - 1; - ensure_approximately_equals("red is at the top", - mScope.getWaveCell(ALScopeData::CH_RED, column, top), 1.f, 5); - ensure_approximately_equals("blue is at the bottom", - mScope.getWaveCell(ALScopeData::CH_BLUE, column, 0), 1.f, 5); - ensure_equals("red is not also at the bottom", - mScope.getWaveCell(ALScopeData::CH_RED, column, 0), 0.f); - ensure("green is at neither end", - mScope.getWaveCell(ALScopeData::CH_GREEN, column, 0) == 0.f && - mScope.getWaveCell(ALScopeData::CH_GREEN, column, top) == 0.f); + CHECK_THAT(mScope.getWaveCell(ALScopeData::CH_RED, column, top), ll_test::WithinBits(1.f, 5)); + CHECK_THAT(mScope.getWaveCell(ALScopeData::CH_BLUE, column, 0), ll_test::WithinBits(1.f, 5)); + CHECK(mScope.getWaveCell(ALScopeData::CH_RED, column, 0) == 0.f); + CHECK(mScope.getWaveCell(ALScopeData::CH_GREEN, column, 0) == 0.f); + CHECK(mScope.getWaveCell(ALScopeData::CH_GREEN, column, top) == 0.f); } // A plot walks the whole grid without checking, so out-of-range reads and a // scope that has never measured anything both have to answer zero rather // than index a vector that is deliberately empty until first use. - template<> template<> - void scope_object::test<25>() + TEST_CASE_METHOD(scope_data, "ALScopeData: waveform reads answer zero when unmeasured or cleared or out of range", "[ALScopeData]") { ALScopeData fresh; - ensure_equals("unmeasured reads zero", fresh.getWaveCell(ALScopeData::CH_LUMA, 0, 0), 0.f); - ensure_equals("unmeasured has no peak", fresh.getWavePeak(ALScopeData::CH_LUMA), 0.f); + CHECK(fresh.getWaveCell(ALScopeData::CH_LUMA, 0, 0) == 0.f); + CHECK(fresh.getWavePeak(ALScopeData::CH_LUMA) == 0.f); mScope.accumulate(flat(200, 200, 200, 16).data(), 16, 1); - ensure_equals("negative column", mScope.getWaveCell(ALScopeData::CH_LUMA, -1, 0), 0.f); - ensure_equals("column past the end", - mScope.getWaveCell(ALScopeData::CH_LUMA, ALScopeData::WAVE_COLUMNS, 0), 0.f); - ensure_equals("level past the end", - mScope.getWaveCell(ALScopeData::CH_LUMA, 0, ALScopeData::WAVE_LEVELS), 0.f); - ensure_equals("bad channel", mScope.getWaveCell(ALScopeData::CH_COUNT, 0, 0), 0.f); + CHECK(mScope.getWaveCell(ALScopeData::CH_LUMA, -1, 0) == 0.f); + CHECK(mScope.getWaveCell(ALScopeData::CH_LUMA, ALScopeData::WAVE_COLUMNS, 0) == 0.f); + CHECK(mScope.getWaveCell(ALScopeData::CH_LUMA, 0, ALScopeData::WAVE_LEVELS) == 0.f); + CHECK(mScope.getWaveCell(ALScopeData::CH_COUNT, 0, 0) == 0.f); // And clearing has to let go of the grid again. mScope.clear(); - ensure_equals("cleared reads zero", mScope.getWaveCell(ALScopeData::CH_LUMA, 0, 0), 0.f); - ensure_equals("cleared has no peak", mScope.getWavePeak(ALScopeData::CH_LUMA), 0.f); + CHECK(mScope.getWaveCell(ALScopeData::CH_LUMA, 0, 0) == 0.f); + CHECK(mScope.getWavePeak(ALScopeData::CH_LUMA) == 0.f); } } diff --git a/indra/newview/tests/alscriptanalysislane_test.cpp b/indra/newview/tests/alscriptanalysislane_test.cpp index 1bbdf7d2b1..033f4a8a6a 100644 --- a/indra/newview/tests/alscriptanalysislane_test.cpp +++ b/indra/newview/tests/alscriptanalysislane_test.cpp @@ -27,7 +27,7 @@ #include "../alscriptanalysislane.h" #include "../alscriptanalyzers.h" -#include "../test/lltut.h" +#include #include "Luau/Cancellation.h" @@ -38,8 +38,6 @@ #include #include -namespace tut -{ namespace { // What the lanes answered, in the order they did, from their threads. @@ -151,7 +149,6 @@ namespace bool mStops = false; bool mStopped = false; }; -} // anonymous namespace struct alscriptanalysislane_data { @@ -187,121 +184,123 @@ namespace } }; - typedef test_group alscriptanalysislane_group; - typedef alscriptanalysislane_group::object alscriptanalysislane_object; - alscriptanalysislane_group alscriptanalysislane_instance("alscriptanalysislane"); - - template<> template<> - void alscriptanalysislane_object::test<1>() + TEST_CASE_METHOD(alscriptanalysislane_data, "alscriptanalysislane: a long check on one lane holds up nothing on the other", "[alscriptanalysislane]") { - set_test_name("a long check on one lane holds nothing of the other's: each engine its own thread and queue"); auto gate = std::make_shared(); ALScriptAnalysisLane luau("TestLuau", [gate] { return std::make_unique(gate, false); }, main()); ALScriptAnalysisLane lsl("TestLSL", [gate] { return std::make_unique(gate, false); }, main()); - ensure("taken", post(luau, job("slow", ALScriptAnalysis::Kind::Check, true))); - ensure("held", gate->waitEntered()); - ensure("taken too", post(lsl, job("other", ALScriptAnalysis::Kind::Hover, true))); - ensure("the other lane's question answered while the check is held", answers.waitFor(1)); - ensure_equals("it alone", answers.all(), std::string("other/2")); + CHECK(post(luau, job("slow", ALScriptAnalysis::Kind::Check, true))); + CHECK(gate->waitEntered()); + CHECK(post(lsl, job("other", ALScriptAnalysis::Kind::Hover, true))); + UNSCOPED_INFO("the other lane's question answered while the check is held"); + CHECK(answers.waitFor(1)); + CHECK(answers.all() == std::string("other/2")); gate->release(); - ensure("then the check", answers.waitFor(2)); - ensure_equals("in that order", answers.all(), std::string("other/2 slow/0")); + UNSCOPED_INFO("then the check"); + CHECK(answers.waitFor(2)); + CHECK(answers.all() == std::string("other/2 slow/0")); luau.close(); lsl.close(); - ensure("closed: refused", !post(luau, job("late", ALScriptAnalysis::Kind::Hover, true))); + UNSCOPED_INFO("closed: refused"); + CHECK_FALSE(post(luau, job("late", ALScriptAnalysis::Kind::Hover, true))); } - template<> template<> - void alscriptanalysislane_object::test<2>() + TEST_CASE_METHOD(alscriptanalysislane_data, "alscriptanalysislane: another tab's check gives way to the front tab's question and reruns", "[alscriptanalysislane]") { - set_test_name("another tab's check gives way to a question of the tab in front, waits again behind it, and answers after"); auto gate = std::make_shared(); ALScriptAnalysisLane luau("TestLuau", [gate] { return std::make_unique(gate, true); }, main()); - ensure("taken", post(luau, job("slow", ALScriptAnalysis::Kind::Check, false))); - ensure("held", gate->waitEntered()); - ensure("asked", post(luau, job("front", ALScriptAnalysis::Kind::Complete, true))); - ensure("both answered", answers.waitFor(2)); - ensure_equals("the question first, then the check, run again", answers.all(), std::string("front/1 slow/0")); + CHECK(post(luau, job("slow", ALScriptAnalysis::Kind::Check, false))); + CHECK(gate->waitEntered()); + CHECK(post(luau, job("front", ALScriptAnalysis::Kind::Complete, true))); + CHECK(answers.waitFor(2)); + UNSCOPED_INFO("the question first, then the check, run again"); + CHECK(answers.all() == std::string("front/1 slow/0")); luau.close(); } - template<> template<> - void alscriptanalysislane_object::test<3>() + TEST_CASE_METHOD(alscriptanalysislane_data, "alscriptanalysislane: the front tab's own check runs on through its questions", "[alscriptanalysislane]") { - set_test_name("the front tab's own check runs on through its questions, and is answered as it was asked"); auto gate = std::make_shared(); ALScriptAnalysisLane luau("TestLuau", [gate] { return std::make_unique(gate, true); }, main()); - ensure("taken", post(luau, job("slow", ALScriptAnalysis::Kind::Check, true))); - ensure("held", gate->waitEntered()); - ensure("asked", post(luau, job("front", ALScriptAnalysis::Kind::Complete, true))); + CHECK(post(luau, job("slow", ALScriptAnalysis::Kind::Check, true))); + CHECK(gate->waitEntered()); + CHECK(post(luau, job("front", ALScriptAnalysis::Kind::Complete, true))); gate->release(); - ensure("both answered", answers.waitFor(2)); - ensure_equals("the check first, not stopped", answers.all(), std::string("slow/0 front/1")); + CHECK(answers.waitFor(2)); + UNSCOPED_INFO("the check first, not stopped"); + CHECK(answers.all() == std::string("slow/0 front/1")); luau.close(); } - template<> template<> - void alscriptanalysislane_object::test<4>() + TEST_CASE_METHOD(alscriptanalysislane_data, "alscriptanalysislane: another tab's check gives way once and its rerun holds the next question", "[alscriptanalysislane]") { - set_test_name("another tab's check gives way once: run again, it runs through the next question of the tab in front, which waits for it"); auto gate = std::make_shared(); gate->always = true; ALScriptAnalysisLane luau("TestLuau", [gate] { return std::make_unique(gate, true); }, main()); - ensure("taken", post(luau, job("slow", ALScriptAnalysis::Kind::Check, false))); - ensure("held", gate->waitEntered()); - ensure("asked", post(luau, job("front", ALScriptAnalysis::Kind::Complete, true))); - ensure("the question answered", answers.waitFor(1)); - ensure("the check run again, and held", gate->waitEntered(2)); - ensure("asked again", post(luau, job("again", ALScriptAnalysis::Kind::Hover, true))); + CHECK(post(luau, job("slow", ALScriptAnalysis::Kind::Check, false))); + CHECK(gate->waitEntered()); + CHECK(post(luau, job("front", ALScriptAnalysis::Kind::Complete, true))); + UNSCOPED_INFO("the question answered"); + CHECK(answers.waitFor(1)); + UNSCOPED_INFO("the check run again, and held"); + CHECK(gate->waitEntered(2)); + CHECK(post(luau, job("again", ALScriptAnalysis::Kind::Hover, true))); // A moment, in which a question that stopped it would be answered. std::this_thread::sleep_for(std::chrono::milliseconds(200)); - ensure_equals("the second question waits for the check", answers.all(), std::string("front/1")); - ensure_equals("not started over", gate->enteredNow(), 2); + UNSCOPED_INFO("the second question waits for the check"); + CHECK(answers.all() == std::string("front/1")); + UNSCOPED_INFO("not started over"); + CHECK(gate->enteredNow() == 2); gate->release(); - ensure("all answered", answers.waitFor(3)); - ensure_equals("the check, then the question", answers.all(), std::string("front/1 slow/0 again/2")); + CHECK(answers.waitFor(3)); + CHECK(answers.all() == std::string("front/1 slow/0 again/2")); luau.close(); } - template<> template<> - void alscriptanalysislane_object::test<5>() + TEST_CASE_METHOD(alscriptanalysislane_data, "alscriptanalysislane: a tab let go of mid-check has the check stopped unanswered and not rerun", "[alscriptanalysislane]") { - set_test_name("a tab let go of while its check runs: the check stopped, answered to nobody, and not run again"); auto gate = std::make_shared(); ALScriptAnalysisLane luau("TestLuau", [gate] { return std::make_unique(gate, true); }, main()); - ensure("taken", post(luau, job("slow", ALScriptAnalysis::Kind::Check, false))); - ensure("held", gate->waitEntered()); + CHECK(post(luau, job("slow", ALScriptAnalysis::Kind::Check, false))); + CHECK(gate->waitEntered()); luau.forget("slow"); - ensure("asked", post(luau, job("front", ALScriptAnalysis::Kind::Hover, true))); - ensure("the question answered", answers.waitFor(1)); + CHECK(post(luau, job("front", ALScriptAnalysis::Kind::Hover, true))); + UNSCOPED_INFO("the question answered"); + CHECK(answers.waitFor(1)); gate->release(); - ensure("asked again", post(luau, job("last", ALScriptAnalysis::Kind::Hover, true))); - ensure("answered", answers.waitFor(2)); - ensure_equals("nothing for the tab let go of", answers.all(), std::string("front/2 last/2")); - ensure_equals("run once", gate->enteredNow(), 1); + CHECK(post(luau, job("last", ALScriptAnalysis::Kind::Hover, true))); + CHECK(answers.waitFor(2)); + UNSCOPED_INFO("nothing for the tab let go of"); + CHECK(answers.all() == std::string("front/2 last/2")); + UNSCOPED_INFO("run once"); + CHECK(gate->enteredNow() == 1); luau.close(); } - template<> template<> - void alscriptanalysislane_object::test<6>() + TEST_CASE_METHOD(alscriptanalysislane_data, "alscriptanalysislane: lookups and other tabs' checks yield to the front tab but not LSL or weighs", "[alscriptanalysislane]") { - set_test_name("a lookup's question of another script gives way to the tab in front as another tab's check does; the front tab's own, LSL's and a weigh do not"); using Kind = ALScriptAnalysis::Kind; - ensure("another tab's check", ALScriptAnalysisLane::yieldsOf(job("x", Kind::Check, false).request)); - ensure("a lookup's", ALScriptAnalysisLane::yieldsOf(job("lookup:a:b", Kind::References, false).request)); - ensure("not the front tab's", !ALScriptAnalysisLane::yieldsOf(job("x", Kind::Check, true).request)); - ensure("not a weigh", !ALScriptAnalysisLane::yieldsOf(job("x", Kind::Weigh, false).request)); + UNSCOPED_INFO("another tab's check"); + CHECK(ALScriptAnalysisLane::yieldsOf(job("x", Kind::Check, false).request)); + UNSCOPED_INFO("a lookup's"); + CHECK(ALScriptAnalysisLane::yieldsOf(job("lookup:a:b", Kind::References, false).request)); + UNSCOPED_INFO("not the front tab's"); + CHECK_FALSE(ALScriptAnalysisLane::yieldsOf(job("x", Kind::Check, true).request)); + UNSCOPED_INFO("not a weigh"); + CHECK_FALSE(ALScriptAnalysisLane::yieldsOf(job("x", Kind::Weigh, false).request)); ALScriptAnalysisLane::Job lsl = job("x", Kind::Check, false); lsl.request.lua = false; - ensure("not LSL's", !ALScriptAnalysisLane::yieldsOf(lsl.request)); + UNSCOPED_INFO("not LSL's"); + CHECK_FALSE(ALScriptAnalysisLane::yieldsOf(lsl.request)); auto gate = std::make_shared(); ALScriptAnalysisLane luau("TestLuau", [gate] { return std::make_unique(gate, true); }, main()); - ensure("taken", post(luau, job("slow", Kind::References, false))); - ensure("held", gate->waitEntered()); - ensure("asked", post(luau, job("front", Kind::Complete, true))); - ensure("both answered", answers.waitFor(2)); - ensure_equals("the question first, then the lookup's, run again", answers.all(), std::string("front/1 slow/5")); + CHECK(post(luau, job("slow", Kind::References, false))); + CHECK(gate->waitEntered()); + CHECK(post(luau, job("front", Kind::Complete, true))); + CHECK(answers.waitFor(2)); + UNSCOPED_INFO("the question first, then the lookup's, run again"); + CHECK(answers.all() == std::string("front/1 slow/5")); luau.close(); } } diff --git a/indra/newview/tests/alscriptanalyzers_test.cpp b/indra/newview/tests/alscriptanalyzers_test.cpp index e9e21ce246..5e380e0503 100644 --- a/indra/newview/tests/alscriptanalyzers_test.cpp +++ b/indra/newview/tests/alscriptanalyzers_test.cpp @@ -26,9 +26,9 @@ #include "../alscriptanalyzers.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { struct alscriptanalyzers_data { @@ -46,47 +46,53 @@ namespace tut } }; - typedef test_group alscriptanalyzers_group; - typedef alscriptanalyzers_group::object alscriptanalyzers_object; - alscriptanalyzers_group alscriptanalyzers_instance("alscriptanalyzers"); - - template<> template<> - void alscriptanalyzers_object::test<1>() + TEST_CASE_METHOD(alscriptanalyzers_data, "alscriptanalyzers: an LSL check reports its parse and a later weigh reports its own", "[alscriptanalyzers]") { - set_test_name("an LSL check says whether its text parsed; a weigh after it, which asks the compiler and not the service, says nothing of another text's"); ALLSLAnalyzer lsl; const ALScriptAnalysis::Request broken = request(ALScriptAnalysis::Kind::Check, "default { state_entry() { llSay(0, \"x\" } }\n"); ALScriptAnalysis::Result checked; lsl.answer(broken, *broken.text, setup, checked); - ensure("builtins loaded: " + checked.definitionsError, checked.definitionsError.empty()); - ensure("the broken text did not parse", !checked.parsed); + UNSCOPED_INFO("builtins loaded: " << checked.definitionsError); + REQUIRE(checked.definitionsError.empty()); + CHECK_FALSE(checked.parsed); ALScriptAnalysis::Request weighing = request(ALScriptAnalysis::Kind::Weigh, "default { state_entry() { llSay(0, \"x\"); } }\n"); weighing.targets = { ALScriptWeight::Target::Mono }; ALScriptAnalysis::Result weighed; lsl.answer(weighing, *weighing.text, setup, weighed); - ensure_equals("weighed", weighed.weights.size(), size_t(1)); - ensure("not the check's parse", weighed.parsed); + REQUIRE(weighed.weights.size() == size_t(1)); + UNSCOPED_INFO("not the check's parse"); + CHECK(weighed.parsed); } - template<> template<> - void alscriptanalyzers_object::test<2>() + TEST_CASE_METHOD(alscriptanalyzers_data, "alscriptanalyzers: a shape outlines LSL states and events and untyped SLua functions", "[alscriptanalyzers]") { - set_test_name("a shape: what a text declares from its parse, LSL's states and events, SLua's functions with no types and nothing loaded for them"); ALLSLAnalyzer lsl; const ALScriptAnalysis::Request shaped = request(ALScriptAnalysis::Kind::Shape, "go() {}\ndefault {\n state_entry() { go(); }\n}\n"); ALScriptAnalysis::Result lsl_shape; lsl.answer(shaped, *shaped.text, setup, lsl_shape); - ensure_equals("LSL: the function, the state, its event", lsl_shape.outline.size(), size_t(3)); - ensure("the event in its state", lsl_shape.outline[1].kind == ALScriptSymbolKind::State && lsl_shape.outline[2].name == "state_entry" && - lsl_shape.outline[2].depth == 1); - ensure("nothing else answered", lsl_shape.problems.empty()); + UNSCOPED_INFO("LSL: the function, the state, its event"); + REQUIRE(lsl_shape.outline.size() == size_t(3)); + { + INFO("the event in its state"); + CHECK(lsl_shape.outline[1].kind == ALScriptSymbolKind::State); + CHECK(lsl_shape.outline[2].name == "state_entry"); + CHECK(lsl_shape.outline[2].depth == 1); + } + UNSCOPED_INFO("nothing else answered"); + CHECK(lsl_shape.problems.empty()); ALLuauAnalyzer luau; ALScriptAnalysis::Request slua = request(ALScriptAnalysis::Kind::Shape, "local function half(n: number)\n return n / 2\nend\n"); slua.lua = true; ALScriptAnalysis::Result luau_shape; luau.answer(slua, *slua.text, ALScriptAnalyzer::Setup(), luau_shape); - ensure("SLua: the function, untyped", luau_shape.outline.size() == 1 && luau_shape.outline[0].name == "half" && luau_shape.outline[0].detail.empty()); - ensure("no definitions asked for", luau_shape.definitionsError.empty()); + { + INFO("SLua: the function, untyped"); + REQUIRE(luau_shape.outline.size() == 1); + CHECK(luau_shape.outline[0].name == "half"); + CHECK(luau_shape.outline[0].detail.empty()); + } + UNSCOPED_INFO("no definitions asked for"); + CHECK(luau_shape.definitionsError.empty()); } } diff --git a/indra/newview/tests/alscriptcontentsindex_test.cpp b/indra/newview/tests/alscriptcontentsindex_test.cpp index b0b24ace3d..31c5289d54 100644 --- a/indra/newview/tests/alscriptcontentsindex_test.cpp +++ b/indra/newview/tests/alscriptcontentsindex_test.cpp @@ -26,7 +26,7 @@ #include "../alscriptcontentsindex.h" -#include "../test/lltut.h" +#include #include #include @@ -34,7 +34,7 @@ #include #include -namespace tut +namespace { struct alscriptcontentsindex_data { @@ -144,66 +144,84 @@ namespace tut return out; } }; - typedef test_group alscriptcontentsindex_group; - typedef alscriptcontentsindex_group::object alscriptcontentsindex_object; - tut::alscriptcontentsindex_group alscriptcontentsindex_instance("alscriptcontentsindex"); - template<> template<> - void alscriptcontentsindex_object::test<1>() + TEST_CASE_METHOD(alscriptcontentsindex_data, "alscriptcontentsindex: a prim is asked once for all who ask and not again while current", "[alscriptcontentsindex]") { - set_test_name("a prim is asked once however many ask, every one hears the answer, and one known and current is not asked again"); const LLUUID chair = object(10); S32 heard = 0; index.onHeard([&heard, chair](const Index::Contents& contents) { heard += contents.prim == chair; }); - ensure("asked", index.ask(chair)); - ensure("not again while it is not answered", !index.ask(chair)); - ensure("asking", index.asking(chair)); - ensure_equals(asked.size(), size_t(1)); - ensure("not known yet", !index.fetched(chair) && index.prim(chair) == nullptr); - ensure("answered", answer(chair, { "sit.lsl", "readme" }, true, "Chair")); - ensure_equals("heard once", heard, 1); - ensure_equals(names(chair), std::string("sit.lsl readme")); - ensure_equals("its name", index.prim(chair)->name, std::string("Chair")); - ensure("no longer asking", !index.asking(chair)); + CHECK(index.ask(chair)); + UNSCOPED_INFO("not again while it is not answered"); + CHECK_FALSE(index.ask(chair)); + CHECK(index.asking(chair)); + CHECK(asked.size() == size_t(1)); + CHECK_FALSE(index.fetched(chair)); + CHECK(index.prim(chair) == nullptr); + // Unanswered, the prim would not be known, and it is read below. + REQUIRE(answer(chair, { "sit.lsl", "readme" }, true, "Chair")); + CHECK(heard == 1); + CHECK(names(chair) == std::string("sit.lsl readme")); + CHECK(index.prim(chair)->name == std::string("Chair")); + CHECK_FALSE(index.asking(chair)); // Known: asked again only where the object's copy is not current, // where asked again by a person, or of the region. - ensure("not current, asked", index.ask(chair) && answer(chair, { "sit.lsl" })); + { + INFO("not current, asked"); + CHECK(index.ask(chair)); + CHECK(answer(chair, { "sit.lsl" })); + } current.insert(chair); - ensure("current, not asked", !index.ask(chair)); - ensure("a refetch is", index.ask(chair, true) && answer(chair, { "sit.lsl" })); - ensure("and of the region", index.ask(chair, false, true) && asked.back().second); - ensure("a refetch while asked asks again", index.ask(chair, true)); + UNSCOPED_INFO("current, not asked"); + CHECK_FALSE(index.ask(chair)); + { + INFO("a refetch is asked"); + CHECK(index.ask(chair, true)); + CHECK(answer(chair, { "sit.lsl" })); + } + { + INFO("so is one of the region"); + CHECK(index.ask(chair, false, true)); + CHECK(asked.back().second); + } + UNSCOPED_INFO("a refetch while asked asks again"); + CHECK(index.ask(chair, true)); } - template<> template<> - void alscriptcontentsindex_object::test<2>() + TEST_CASE_METHOD(alscriptcontentsindex_data, "alscriptcontentsindex: a missed answer keeps the contents and a script's running is asked once", "[alscriptcontentsindex]") { - set_test_name("an answer that did not come leaves what was known; each script is asked once whether it runs, until let go of"); const LLUUID chair = object(10); index.ask(chair); answer(chair, { "sit.lsl", "pose.lsl", "readme" }); - ensure_equals("each script asked whether it runs", askedRunning.size(), size_t(2)); + UNSCOPED_INFO("each script asked whether it runs"); + REQUIRE(askedRunning.size() == size_t(2)); index.running(askedRunning[0], true); - ensure("known", index.running(askedRunning[0]) == std::optional(true)); - ensure("not known", !index.running(askedRunning[1])); + CHECK(index.running(askedRunning[0]) == std::optional(true)); + UNSCOPED_INFO("one not answered is not known"); + CHECK_FALSE(index.running(askedRunning[1])); index.ask(chair, true); answer(chair, {}, false); - ensure_equals("no answer is not an empty prim", names(chair), std::string("sit.lsl pose.lsl readme")); - ensure("still fetched", index.fetched(chair)); - ensure_equals("only the one not known asked again", askedRunning.size(), size_t(3)); + UNSCOPED_INFO("no answer is not an empty prim"); + CHECK(names(chair) == std::string("sit.lsl pose.lsl readme")); + UNSCOPED_INFO("still fetched"); + CHECK(index.fetched(chair)); + UNSCOPED_INFO("only the one not known asked again"); + CHECK(askedRunning.size() == size_t(3)); index.forgetRunning({ chair }); - ensure("let go of", !index.running(askedRunning[0])); + UNSCOPED_INFO("whether it runs let go of"); + CHECK_FALSE(index.running(askedRunning[0])); index.forget(chair); - ensure("all of it", index.prim(chair) == nullptr && names(chair).empty()); + { + INFO("all of the prim let go of"); + CHECK(index.prim(chair) == nullptr); + CHECK(names(chair).empty()); + } } - template<> template<> - void alscriptcontentsindex_object::test<3>() + TEST_CASE_METHOD(alscriptcontentsindex_data, "alscriptcontentsindex: ensureListed tells once every prim has answered and names the unlisted", "[alscriptcontentsindex]") { - set_test_name("an object listed whole: every prim asked, told once all have answered, and which could not be listed said"); const LLUUID house = object(20, 4); // A prim known and current is not asked; one asked by someone else // is waited for. @@ -215,16 +233,25 @@ namespace tut std::optional listed; index.ensureListed(id(23), [&listed](const Index::Listed& out) { listed = out; }); - ensure_equals("the root and the one never asked", asked.size() - before, size_t(2)); - ensure("not yet", !listed); + UNSCOPED_INFO("the root and the one never asked"); + CHECK(asked.size() - before == size_t(2)); + UNSCOPED_INFO("not told yet"); + CHECK_FALSE(listed); answer(id(20), { "main.lsl" }); answer(id(22), { "light.lsl" }); - ensure("not while one is out", !listed); + UNSCOPED_INFO("not while one is out"); + CHECK_FALSE(listed); answer(id(23), {}, false); - ensure("told", listed.has_value()); - ensure("by its root", listed->root == house && listed->present); - ensure_equals("every prim", listed->prims.size(), size_t(4)); - ensure("the one that did not answer said", listed->unlisted == std::vector{ id(23) }); + REQUIRE(listed.has_value()); + { + INFO("by its root"); + CHECK(listed->root == house); + CHECK(listed->present); + } + UNSCOPED_INFO("every prim"); + CHECK(listed->prims.size() == size_t(4)); + UNSCOPED_INFO("the one that did not answer said"); + CHECK(listed->unlisted == std::vector{ id(23) }); // Everything known and current: told at once. for (U32 i = 20; i < 24; ++i) @@ -235,18 +262,25 @@ namespace tut answer(id(23), {}); listed.reset(); index.ensureListed(house, [&listed](const Index::Listed& out) { listed = out; }); - ensure("at once", listed.has_value() && listed->unlisted.empty()); + { + INFO("at once"); + REQUIRE(listed.has_value()); + CHECK(listed->unlisted.empty()); + } // Not in sight: no prims, not present, told at once. listed.reset(); index.ensureListed(id(99), [&listed](const Index::Listed& out) { listed = out; }); - ensure("away", listed.has_value() && !listed->present && listed->prims.empty()); + { + INFO("not in sight"); + REQUIRE(listed.has_value()); + CHECK_FALSE(listed->present); + CHECK(listed->prims.empty()); + } } - template<> template<> - void alscriptcontentsindex_object::test<4>() + TEST_CASE_METHOD(alscriptcontentsindex_data, "alscriptcontentsindex: an answer on the spot reaches the wait and a listener may list again", "[alscriptcontentsindex]") { - set_test_name("a world that answers on the spot answers into the wait, and one told may list again"); const LLUUID shed = object(30, 2); Index::World world; world.linkset = [this](const LLUUID& any) { return objects[any]; }; @@ -261,59 +295,79 @@ namespace tut S32 told = 0; spot.ensureListed(shed, [&](const Index::Listed& listed) { ++told; - ensure("all listed", listed.unlisted.empty() && listed.prims.size() == 2); + { + INFO("all listed"); + REQUIRE(listed.unlisted.empty()); + REQUIRE(listed.prims.size() == 2); + } if (told == 1) { spot.ensureListed(shed, [&told](const Index::Listed&) { ++told; }); } }); - ensure_equals("each told once", told, 2); + UNSCOPED_INFO("each told once"); + CHECK(told == 2); } - template<> template<> - void alscriptcontentsindex_object::test<5>() + // The studio's draw looks once a second, and asks a prim at most every + // few seconds. + TEST_CASE_METHOD(alscriptcontentsindex_data, "alscriptcontentsindex: refresh asks a prim again once its serial moved or its copy went", "[alscriptcontentsindex]") { - set_test_name("what a prim holds asked again as the studio draws once it changed -- its serial moved, or its object's copy was let " - "go of -- looking once a second, and a prim at most every few seconds"); const LLUUID chair = object(40); current.insert(chair); serials[chair] = 3; index.ask(chair); answer(chair, { "sit.lsl" }); index.refresh(10.0); - ensure("unchanged: not asked", pending.empty()); + UNSCOPED_INFO("unchanged: not asked"); + CHECK(pending.empty()); // A script added from the build tools. serials[chair] = 4; index.refresh(10.5); - ensure("not looked at again within the second", pending.empty()); + UNSCOPED_INFO("not looked at again within the second"); + CHECK(pending.empty()); index.refresh(11.0); - ensure("its serial moved: asked", pending.size() == 1 && asked.back().first == chair); + { + INFO("its serial moved: asked"); + CHECK(pending.size() == 1); + CHECK(asked.back().first == chair); + } answer(chair, { "sit.lsl", "new.lsl" }); - ensure_equals("what it holds now", names(chair), std::string("sit.lsl new.lsl")); + UNSCOPED_INFO("what it holds now"); + CHECK(names(chair) == std::string("sit.lsl new.lsl")); index.refresh(12.0); - ensure("heard at its serial: not asked", pending.empty()); + UNSCOPED_INFO("heard at its serial: not asked"); + CHECK(pending.empty()); // Its object's copy let go of, and no answer coming. current.erase(chair); index.refresh(13.0); - ensure("not asked again so soon", pending.empty()); + UNSCOPED_INFO("not asked again so soon"); + CHECK(pending.empty()); index.refresh(16.0); - ensure("asked", pending.size() == 1); + UNSCOPED_INFO("asked"); + CHECK(pending.size() == 1); index.refresh(17.0); - ensure("not while it is asked", pending.size() == 1); + UNSCOPED_INFO("not while it is asked"); + CHECK(pending.size() == 1); answer(chair, {}, false); - ensure_equals("no answer keeps what was known", names(chair), std::string("sit.lsl new.lsl")); + UNSCOPED_INFO("no answer keeps what was known"); + CHECK(names(chair) == std::string("sit.lsl new.lsl")); index.refresh(18.0); - ensure("not asked again at once", pending.empty()); + UNSCOPED_INFO("not asked again at once"); + CHECK(pending.empty()); index.refresh(21.0); - ensure("asked again a while after", pending.size() == 1); + UNSCOPED_INFO("asked again a while after"); + CHECK(pending.size() == 1); answer(chair, { "sit.lsl" }); // Asked for directly, a prim that changed is asked though known. current.insert(chair); - ensure("current: not asked", !index.ask(chair)); + UNSCOPED_INFO("current: not asked"); + CHECK_FALSE(index.ask(chair)); serials[chair] = 5; - ensure("its serial moved: asked", index.ask(chair)); + UNSCOPED_INFO("its serial moved: asked"); + CHECK(index.ask(chair)); } } diff --git a/indra/newview/tests/alscriptcrumbsbar_test.cpp b/indra/newview/tests/alscriptcrumbsbar_test.cpp index ee586112d6..9c71cd0680 100644 --- a/indra/newview/tests/alscriptcrumbsbar_test.cpp +++ b/indra/newview/tests/alscriptcrumbsbar_test.cpp @@ -39,7 +39,7 @@ #include "lltimer.h" #include "lluicolortable.h" -#include "../test/lltut.h" +#include #include @@ -140,7 +140,7 @@ namespace }; } -namespace tut +namespace { struct alscriptcrumbsbar_data { @@ -156,7 +156,7 @@ namespace tut ALScriptCrumbsBar* found = window.find("crumbs"); if (!found) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } return found; } @@ -244,69 +244,82 @@ namespace tut } }; - typedef test_group alscriptcrumbsbar_group; - typedef alscriptcrumbsbar_group::object alscriptcrumbsbar_object; - alscriptcrumbsbar_group alscriptcrumbsbar_instance("alscriptcrumbsbar"); - - template<> template<> - void alscriptcrumbsbar_object::test<1>() + TEST_CASE_METHOD(alscriptcrumbsbar_data, "alscriptcrumbsbar: the caret's path offers each step's siblings and follows changes", "[alscriptcrumbsbar]") { - set_test_name("the path the caret is in, each step offering those beside it; made again where the path, the tab or its name changed"); ALScriptCrumbsBar* crumbs = bar(); Doc& doc = tab("a"); doc.editor->setCaret(ALTextPos(3, 5)); place(crumbs, doc); const std::string top = "Script a [default, f, g, h, other]"; - ensure_equals("the path", path(), top + ", default [default, f, g, h, other], state_entry [state_entry, touch_start]"); - ensure_equals("kept", doc.caret->crumbPath.size(), size_t(2)); - ensure_equals("the script's step", jump()->path()[0].value, std::string("top")); - ensure_equals("said", jump()->path()[0].toolTip, + CHECK(path() == top + ", default [default, f, g, h, other], state_entry [state_entry, touch_start]"); + UNSCOPED_INFO("kept"); + REQUIRE(doc.caret->crumbPath.size() == size_t(2)); + UNSCOPED_INFO("the script's step"); + CHECK(jump()->path()[0].value == std::string("top")); + UNSCOPED_INFO("said"); + CHECK(jump()->path()[0].toolTip == std::string("Script a: click to go to the top; the arrow lists the script's declarations")); - ensure_equals("a symbol's", jump()->path()[2].toolTip, std::string("Go to state_entry; the arrow lists other items at this level")); + UNSCOPED_INFO("a symbol's"); + CHECK(jump()->path()[2].toolTip == std::string("Go to state_entry; the arrow lists other items at this level")); doc.editor->setCaret(ALTextPos(4, 0)); place(crumbs, doc); - ensure("the trailer is", trailer().find("Ln 5, Col 1") != std::string::npos); + CHECK(trailer().find("Ln 5, Col 1") != std::string::npos); doc.editor->setCaret(ALTextPos(11, 0)); place(crumbs, doc); - ensure_equals("in f", path(), top + ", f [default, f, g, h, other]"); + UNSCOPED_INFO("in f"); + CHECK(path() == top + ", f [default, f, g, h, other]"); doc.editor->setCaret(ALTextPos(9, 0)); place(crumbs, doc); - ensure_equals("between: the script", path(), top); + UNSCOPED_INFO("between: the script"); + CHECK(path() == top); doc.editor->setCaret(ALTextPos(4, 5)); place(crumbs, doc); - ensure("at a symbol's very end: still in it", path().find("state_entry [") != std::string::npos); + UNSCOPED_INFO("at a symbol's very end: still in it"); + CHECK(path().find("state_entry [") != std::string::npos); doc.editor->setCaret(ALTextPos(13, 5)); place(crumbs, doc); - ensure_equals("where two meet: the later", jump()->path().back().label, std::string("h")); + UNSCOPED_INFO("where two meet: the later"); + CHECK(jump()->path().back().label == std::string("h")); doc.editor->setCaret(ALTextPos(17, 0)); place(crumbs, doc); - ensure_equals("alone in its state: nothing beside it", path(), top + ", other [default, f, g, h, other], timer"); + UNSCOPED_INFO("alone in its state: nothing beside it"); + CHECK(path() == top + ", other [default, f, g, h, other], timer"); doc.name = "Renamed"; place(crumbs, doc); - ensure_equals("a new name: again", jump()->path()[0].label, std::string("Renamed")); + UNSCOPED_INFO("a new name: again"); + CHECK(jump()->path()[0].label == std::string("Renamed")); Doc& other = tab("b"); other.name = "Other"; place(crumbs, other); - ensure_equals("not in front: nothing", jump()->path()[0].label, std::string("Renamed")); + UNSCOPED_INFO("not in front: nothing"); + CHECK(jump()->path()[0].label == std::string("Renamed")); crumbs->forget(); - ensure("forgotten", jump()->path().empty() && jump()->trailer().empty()); + { + INFO("forgotten"); + CHECK(jump()->path().empty()); + CHECK(jump()->trailer().empty()); + } place(crumbs, doc); - ensure_equals("and shown again whatever", jump()->path()[0].label, std::string("Renamed")); + UNSCOPED_INFO("and shown again whatever"); + CHECK(jump()->path()[0].label == std::string("Renamed")); } - template<> template<> - void alscriptcrumbsbar_object::test<2>() + TEST_CASE_METHOD(alscriptcrumbsbar_data, "alscriptcrumbsbar: the trailer's parts and tips and which go first where it is narrow", "[alscriptcrumbsbar]") { - set_test_name("the trailer: read only, vim's word, the caret, what is selected and the problems, each pressable one with its tip; what goes first where narrow"); ALScriptCrumbsBar* crumbs = bar(); Doc& doc = tab("a"); crumbs->setTips({ "line tip", "problems tip", "source tip", "expanded tip" }); doc.editor->setCaret(ALTextPos(2, 6)); crumbs->showTrailer(doc); - ensure_equals("the caret and the indentation", trailer(), std::string("Ln 3, Col 7, Tab Size: 4")); - ensure("pressed for a line", part("Ln")->value == "line" && part("Ln")->toolTip == "line tip"); + UNSCOPED_INFO("the caret and the indentation"); + CHECK(trailer() == std::string("Ln 3, Col 7, Tab Size: 4")); + { + INFO("pressed for a line"); + CHECK(part("Ln")->value == "line"); + CHECK(part("Ln")->toolTip == "line tip"); + } doc.modifiable = false; told().banner = "-- INSERT --"; @@ -314,36 +327,63 @@ namespace tut told().warnings = 1; doc.editor->setSelection(ALTextRange(ALTextPos(2, 0), ALTextPos(4, 0))); crumbs->showTrailer(doc); - ensure_equals("in order", trailer(), std::string("Read-only, -- INSERT --, Ln 5, Col 1, 2 lines selected, Tab Size: 4, 2 errors, 1 warning")); + UNSCOPED_INFO("in order"); + CHECK(trailer() == std::string("Read-only, -- INSERT --, Ln 5, Col 1, 2 lines selected, Tab Size: 4, 2 errors, 1 warning")); const std::vector& raw = jump()->trailer(); - ensure("a dot between each", raw.size() == 13 && raw[1].text == " \xC2\xB7 " && raw[11].text == raw[1].text); - ensure_equals("the last a part", raw[12].text, std::string("1 warning")); - ensure("the dots only between", raw[1].between && raw[11].between && !raw[12].between); - ensure("where narrow: the indentation first, then the selection, the counts and read only last, the place never", - part("Tab")->drop > part("2 lines")->drop && part("2 lines")->drop > part("2 errors")->drop && part("2 errors")->drop > 0 && - part("Read")->drop > 0 && part("Ln")->drop == 0); - ensure_equals("read only, said", part("Read")->toolTip, std::string("You can view this script but not change it")); - ensure("the problems pressable", part("2 errors")->value == "problems" && part("1 warning")->toolTip == "problems tip"); + { + INFO("a dot between each"); + REQUIRE(raw.size() == 13); + CHECK(raw[1].text == " \xC2\xB7 "); + CHECK(raw[11].text == raw[1].text); + } + UNSCOPED_INFO("the last a part"); + CHECK(raw[12].text == std::string("1 warning")); + { + INFO("the dots only between"); + CHECK(raw[1].between); + CHECK(raw[11].between); + CHECK_FALSE(raw[12].between); + } + { + INFO("where narrow: the indentation first, then the selection, the counts and read only last, the place never"); + CHECK(part("Tab")->drop > part("2 lines")->drop); + CHECK(part("2 lines")->drop > part("2 errors")->drop); + CHECK(part("2 errors")->drop > 0); + CHECK(part("Read")->drop > 0); + CHECK(part("Ln")->drop == 0); + } + UNSCOPED_INFO("read only, said"); + CHECK(part("Read")->toolTip == std::string("You can view this script but not change it")); + { + INFO("the problems pressable"); + CHECK(part("2 errors")->value == "problems"); + CHECK(part("1 warning")->toolTip == "problems tip"); + } doc.editor->setSelection(ALTextRange(ALTextPos(2, 0), ALTextPos(4, 2))); crumbs->showTrailer(doc); - ensure("a line begun counts", part("3 lines") != nullptr); + UNSCOPED_INFO("a line begun counts"); + CHECK(part("3 lines") != nullptr); doc.editor->setText("a\tb\xC3\xA9\n"); doc.editor->setSelection(ALTextRange(ALTextPos(0, 0), ALTextPos(0, 5))); crumbs->showTrailer(doc); - ensure("characters, a tab one and an accented one one", part("4 characters selected") != nullptr); + UNSCOPED_INFO("characters, a tab one and an accented one one"); + CHECK(part("4 characters selected") != nullptr); doc.editor->setCaret(ALTextPos(0, 2)); crumbs->showTrailer(doc); - ensure_equals("the column as seen, past the tab", part("Ln")->text, + UNSCOPED_INFO("the column as seen, past the tab"); + CHECK(part("Ln")->text == "Ln 1, Col " + std::to_string(doc.editor->getTabWidth() + 1)); doc.loaded = false; crumbs->showTrailer(doc); - ensure("not loaded: not said read only, nor how it is indented", part("Read") == nullptr && part("Tab") == nullptr); + { + INFO("not loaded: not said read only, nor how it is indented"); + CHECK(part("Read") == nullptr); + CHECK(part("Tab") == nullptr); + } } - template<> template<> - void alscriptcrumbsbar_object::test<3>() + TEST_CASE_METHOD(alscriptcrumbsbar_data, "alscriptcrumbsbar: code and save sizes coloured near their limits and the view pressable", "[alscriptcrumbsbar]") { - set_test_name("its code size, coloured near and past its limit; what a save would send, past half; the view, pressable"); ALScriptCrumbsBar* crumbs = bar(); Doc& doc = tab("a"); const LLColor4 error = doc.editor->markColor(ALCodeEditor::Mark::Error); @@ -355,41 +395,61 @@ namespace tut doc.weighing->weight = weight; crumbs->showTrailer(doc); const Part* weighed = part("LSL (Mono)"); - ensure("weighed", weighed && weighed->text == "LSL (Mono) 40.0 of 64 KB used" && !weighed->color); - ensure("before the optimizer", weighed->toolTip.find("Measured before the preprocessor") != std::string::npos); + { + INFO("weighed"); + REQUIRE(weighed); + CHECK(weighed->text == "LSL (Mono) 40.0 of 64 KB used"); + CHECK_FALSE(weighed->color); + } + UNSCOPED_INFO("before the optimizer"); + CHECK(weighed->toolTip.find("Measured before the preprocessor") != std::string::npos); doc.weighing->exact = true; doc.weighing->sent = true; doc.weighing->weight->total = 60000; crumbs->showTrailer(doc); - ensure("as sent", part("LSL (Mono)")->toolTip.find("as it is saved") != std::string::npos); - ensure("near: the warning's", part("LSL (Mono)")->color == warn); + UNSCOPED_INFO("as sent"); + CHECK(part("LSL (Mono)")->toolTip.find("as it is saved") != std::string::npos); + UNSCOPED_INFO("near: the warning's"); + CHECK(part("LSL (Mono)")->color == warn); doc.weighing->weight->total = 70000; crumbs->showTrailer(doc); - ensure("past: the error's", part("LSL (Mono)")->color == error); + UNSCOPED_INFO("past: the error's"); + CHECK(part("LSL (Mono)")->color == error); doc.weighing->weight->estimate = true; crumbs->showTrailer(doc); - ensure("an estimate", part("LSL (Mono) ~") != nullptr); + UNSCOPED_INFO("an estimate"); + CHECK(part("LSL (Mono) ~") != nullptr); doc.weighing->weight.reset(); const size_t LIMIT = ALScriptEnvelope::MAX_ASSET_BYTES; doc.weighing->assetBytes = LIMIT / 2; crumbs->showTrailer(doc); - ensure("half: nothing", parts().size() == 2); + UNSCOPED_INFO("half: nothing"); + CHECK(parts().size() == 2); doc.weighing->assetBytes = LIMIT / 2 + 1; crumbs->showTrailer(doc); - ensure("past half", part(std::to_string((LIMIT / 2 + 1 + 1023) / 1024) + " KB") != nullptr); - ensure("said", parts().back().toolTip.find("Save size") != std::string::npos && !parts().back().color); + UNSCOPED_INFO("past half"); + REQUIRE(part(std::to_string((LIMIT / 2 + 1 + 1023) / 1024) + " KB") != nullptr); + { + INFO("said"); + CHECK(parts().back().toolTip.find("Save size") != std::string::npos); + CHECK_FALSE(parts().back().color); + } doc.weighing->assetBytes = LIMIT * 9 / 10 + 1; crumbs->showTrailer(doc); - ensure("near: the warning's", parts().back().color == warn); + UNSCOPED_INFO("near: the warning's"); + CHECK(parts().back().color == warn); doc.weighing->assetBytes = LIMIT + 1; crumbs->showTrailer(doc); - ensure("over: the error's", parts().back().color == error); - ensure("and by how much", parts().back().toolTip.find("1 over the") != std::string::npos); + UNSCOPED_INFO("over: the error's"); + CHECK(parts().back().color == error); + UNSCOPED_INFO("and by how much"); + CHECK(parts().back().toolTip.find("1 over the") != std::string::npos); doc.notecard = true; doc.weighing->assetBytes = static_cast(LLNotecard::MAX_SIZE) / 2 + 1; crumbs->showTrailer(doc); - ensure("a notecard's by its own limit", parts().back().toolTip.find("Notecard text") != std::string::npos); + UNSCOPED_INFO("a notecard's by its own limit"); + CHECK(parts().back().toolTip.find("Notecard text") != std::string::npos); doc.notecard = false; doc.weighing->assetBytes = 0; @@ -397,18 +457,26 @@ namespace tut doc.expandedEditor = editor("expanded_a", "integer x;\n"); crumbs->showTrailer(doc); const Part shown = parts().back(); - ensure("the source in front", shown.text == "Source" && shown.value == "expanded" && shown.toolTip == "source tip"); + { + INFO("the source in front"); + CHECK(shown.text == "Source"); + CHECK(shown.value == "expanded"); + CHECK(shown.toolTip == "source tip"); + } doc.view = Doc::View::Expanded; doc.expandedEditor->setCaret(ALTextPos(0, 3)); crumbs->showTrailer(doc); - ensure("the expansion", parts().back().text == "Preprocessed" && parts().back().toolTip == "expanded tip"); - ensure("its own caret", part("Ln 1, Col 4") != nullptr); + { + INFO("the expansion"); + CHECK(parts().back().text == "Preprocessed"); + CHECK(parts().back().toolTip == "expanded tip"); + } + UNSCOPED_INFO("its own caret"); + CHECK(part("Ln 1, Col 4") != nullptr); } - template<> template<> - void alscriptcrumbsbar_object::test<4>() + TEST_CASE_METHOD(alscriptcrumbsbar_data, "alscriptcrumbsbar: the optimizer's sizes replace the weight while the expansion is in front", "[alscriptcrumbsbar]") { - set_test_name("what the optimizer made of it, in place of its weight, while the preprocessed view is in front"); ALScriptCrumbsBar* crumbs = bar(); Doc& doc = tab("a"); ALScriptWeight weight; @@ -425,31 +493,39 @@ namespace tut doc.uploaded.codeAfter = 10240; told().target = ALScriptWeight::Target::Mono; crumbs->showTrailer(doc); - ensure_equals("before and after", part("LSL (Mono)")->text, std::string("LSL (Mono) ~20.0 \xE2\x86\x92 ~10.0 of 64 KB used")); - ensure_equals("once", parts().size(), size_t(4)); + UNSCOPED_INFO("before and after"); + CHECK(part("LSL (Mono)")->text == std::string("LSL (Mono) ~20.0 \xE2\x86\x92 ~10.0 of 64 KB used")); + UNSCOPED_INFO("once"); + CHECK(parts().size() == size_t(4)); doc.uploaded.codeAfter = 60000; crumbs->showTrailer(doc); - ensure("near: the warning's", part("LSL (Mono)")->color == doc.editor->markColor(ALCodeEditor::Mark::Warning)); + UNSCOPED_INFO("near: the warning's"); + CHECK(part("LSL (Mono)")->color == doc.editor->markColor(ALCodeEditor::Mark::Warning)); doc.uploaded.codeAfter = 70000; crumbs->showTrailer(doc); - ensure("past: the error's", part("LSL (Mono)")->color == doc.editor->markColor(ALCodeEditor::Mark::Error)); + UNSCOPED_INFO("past: the error's"); + CHECK(part("LSL (Mono)")->color == doc.editor->markColor(ALCodeEditor::Mark::Error)); told().target = ALScriptWeight::Target::LSO; crumbs->showTrailer(doc); - ensure("measured, not estimated", part("LSL (LSO)") && part("LSL (LSO)")->text.find('~') == std::string::npos); + { + INFO("measured, not estimated"); + REQUIRE(part("LSL (LSO)")); + CHECK(part("LSL (LSO)")->text.find('~') == std::string::npos); + } doc.editor->insertText("x"); crumbs->showTrailer(doc); - ensure("typed since: its weight", part("LSL (Mono) 40.0") != nullptr); + UNSCOPED_INFO("typed since: its weight"); + CHECK(part("LSL (Mono) 40.0") != nullptr); doc.view = Doc::View::Source; doc.uploaded.version = doc.editor->document().version(); told().target = ALScriptWeight::Target::Mono; crumbs->showTrailer(doc); - ensure("the source in front: its weight", part("LSL (Mono) 40.0") != nullptr); + UNSCOPED_INFO("the source in front: its weight"); + CHECK(part("LSL (Mono) 40.0") != nullptr); } - template<> template<> - void alscriptcrumbsbar_object::test<5>() + TEST_CASE_METHOD(alscriptcrumbsbar_data, "alscriptcrumbsbar: a chosen step goes to the top or a symbol's name or nowhere if gone", "[alscriptcrumbsbar]") { - set_test_name("a step chosen: the script's top as a place, a symbol's name as a stretch, one gone as nowhere"); ALScriptCrumbsBar* crumbs = bar(); Doc& doc = tab("a"); crumbs->choose("top"); @@ -457,48 +533,78 @@ namespace tut crumbs->choose("9\ngone"); doc.outline.erase(doc.outline.begin()); crumbs->choose("1\ntouch_start"); - ensure_equals("each", joined(told().chosen), std::string("a 0:0-0:0, a 2:4-2:15, a nowhere, a 5:4-5:15")); + CHECK(joined(told().chosen) == std::string("a 0:0-0:0, a 2:4-2:15, a nowhere, a 5:4-5:15")); } - template<> template<> - void alscriptcrumbsbar_object::test<6>() + TEST_CASE_METHOD(alscriptcrumbsbar_data, "alscriptcrumbsbar: the indentation part names its source and its menu sets this script only", "[alscriptcrumbsbar]") { - set_test_name("how the script is indented: a tab's width or a level's spaces, pressable, the tip saying where it was said; the menu's choices for that script alone"); ALScriptCrumbsBar* crumbs = bar(); Doc& doc = tab("a"); crumbs->showTrailer(doc); - ensure("tabs, by default", part("Tab Size: 4") && part("Tab Size")->value == "indent"); - ensure("the default's tip", part("Tab Size")->toolTip.find("Default indentation from Preferences") != std::string::npos); - ensure("checked as it is", crumbs->indentChecked("tabs") && crumbs->indentChecked("width_4") && !crumbs->indentChecked("spaces")); - ensure("nothing chosen to forget", !crumbs->indentEnabled("read")); + { + INFO("tabs, by default"); + REQUIRE(part("Tab Size: 4")); + CHECK(part("Tab Size")->value == "indent"); + } + UNSCOPED_INFO("the default's tip"); + CHECK(part("Tab Size")->toolTip.find("Default indentation from Preferences") != std::string::npos); + { + INFO("checked as it is"); + CHECK(crumbs->indentChecked("tabs")); + CHECK(crumbs->indentChecked("width_4")); + CHECK_FALSE(crumbs->indentChecked("spaces")); + } + UNSCOPED_INFO("nothing chosen to forget"); + CHECK_FALSE(crumbs->indentEnabled("read")); doc.editor->setReadsIndentation(true); doc.editor->setText("a\n b\n"); crumbs->showTrailer(doc); - ensure("read from the script", part("Spaces: 2") && part("Spaces")->toolTip.find("detected from this script's lines") != std::string::npos); + { + INFO("read from the script"); + REQUIRE(part("Spaces: 2")); + CHECK(part("Spaces")->toolTip.find("detected from this script's lines") != std::string::npos); + } crumbs->indentAct("width_4"); - ensure("a width chosen", part("Spaces: 4") && part("Spaces")->toolTip.find("set for this script") != std::string::npos); - ensure("and checked", crumbs->indentChecked("width_4") && !crumbs->indentChecked("width_2")); - ensure("the text untouched", doc.editor->text() == "a\n b\n"); + { + INFO("a width chosen"); + REQUIRE(part("Spaces: 4")); + CHECK(part("Spaces")->toolTip.find("set for this script") != std::string::npos); + } + { + INFO("and checked"); + CHECK(crumbs->indentChecked("width_4")); + CHECK_FALSE(crumbs->indentChecked("width_2")); + } + UNSCOPED_INFO("the text untouched"); + CHECK(doc.editor->text() == "a\n b\n"); crumbs->indentAct("tabs"); - ensure("tabs chosen", part("Tab Size: 4") != nullptr && crumbs->indentChecked("tabs")); - ensure("to be forgotten", crumbs->indentEnabled("read")); + { + INFO("tabs chosen"); + CHECK(part("Tab Size: 4") != nullptr); + CHECK(crumbs->indentChecked("tabs")); + } + UNSCOPED_INFO("to be forgotten"); + CHECK(crumbs->indentEnabled("read")); crumbs->indentAct("read"); - ensure("the script's own again", part("Spaces: 2") != nullptr); + UNSCOPED_INFO("the script's own again"); + CHECK(part("Spaces: 2") != nullptr); crumbs->indentAct("convert_tabs"); - ensure_equals("converted, a tab of the width", doc.editor->text(), std::string("a\n\tb\n")); - ensure("and tabs from here on", part("Tab Size: 2") != nullptr); + UNSCOPED_INFO("converted, a tab of the width"); + CHECK(doc.editor->text() == std::string("a\n\tb\n")); + UNSCOPED_INFO("and tabs from here on"); + CHECK(part("Tab Size: 2") != nullptr); doc.modifiable = false; - ensure("not a script that may only be read", !crumbs->indentEnabled("convert_spaces")); + UNSCOPED_INFO("not a script that may only be read"); + CHECK_FALSE(crumbs->indentEnabled("convert_spaces")); crumbs->indentAct("convert_spaces"); - ensure_equals("left as it was", doc.editor->text(), std::string("a\n\tb\n")); + UNSCOPED_INFO("left as it was"); + CHECK(doc.editor->text() == std::string("a\n\tb\n")); } - template<> template<> - void alscriptcrumbsbar_object::test<7>() + TEST_CASE_METHOD(alscriptcrumbsbar_data, "alscriptcrumbsbar: a check out a while is said but one just asked or answered is not", "[alscriptcrumbsbar]") { - set_test_name("a check out a while is said, and pressed shows the problems; one just asked, or answered, is not"); ALScriptCrumbsBar* crumbs = bar(); Doc& doc = tab("a"); doc.loaded = true; @@ -507,20 +613,24 @@ namespace tut doc.check->analysisVersion = doc.check->requestedVersion + 1; doc.check->askedAt = now; crumbs->showTrailer(doc); - ensure("just asked: nothing", part("Checking") == nullptr); + UNSCOPED_INFO("just asked: nothing"); + CHECK(part("Checking") == nullptr); doc.check->askedAt = now - 1.0; crumbs->showTrailer(doc); const Part* checking = part("Checking"); - ensure("out a while: said", checking != nullptr && checking->value == "problems"); + { + INFO("out a while: said"); + REQUIRE(checking != nullptr); + CHECK(checking->value == "problems"); + } doc.check->analysisVersion = doc.check->requestedVersion; crumbs->showTrailer(doc); - ensure("answered: gone", part("Checking") == nullptr); + UNSCOPED_INFO("answered: gone"); + CHECK(part("Checking") == nullptr); } - template<> template<> - void alscriptcrumbsbar_object::test<8>() + TEST_CASE_METHOD(alscriptcrumbsbar_data, "alscriptcrumbsbar: only what changed is said again so a caret move says just its place", "[alscriptcrumbsbar]") { - set_test_name("a caret moving says its place and the selection again, and nothing else; the rest said again where what it says changed"); ALScriptCrumbsBar* crumbs = bar(); Doc& doc = tab("a"); told().errors = 2; @@ -529,72 +639,109 @@ namespace tut said = 0; doc.editor->setCaret(ALTextPos(4, 0)); crumbs->showTrailer(doc); - ensure_equals("the place alone said", said, 1); - ensure_equals("and the rest kept", trailer(), std::string("Ln 5, Col 1, Tab Size: 4, 2 errors")); + UNSCOPED_INFO("the place alone said"); + CHECK(said == 1); + UNSCOPED_INFO("and the rest kept"); + CHECK(trailer() == std::string("Ln 5, Col 1, Tab Size: 4, 2 errors")); doc.editor->setSelection(ALTextRange(ALTextPos(2, 0), ALTextPos(4, 0))); said = 0; crumbs->showTrailer(doc); - ensure_equals("and the selection", said, 2); + UNSCOPED_INFO("and the selection"); + CHECK(said == 2); said = 0; told().errors = 3; crumbs->showTrailer(doc); - ensure("counted again: said again", said > 2 && part("3 errors") != nullptr); + { + INFO("counted again: said again"); + CHECK(said > 2); + REQUIRE(part("3 errors") != nullptr); + } said = 0; doc.editor->setTabWidth(2); crumbs->showTrailer(doc); - ensure("indented otherwise", said > 2 && part("Tab Size: 2") != nullptr); + { + INFO("indented otherwise"); + CHECK(said > 2); + CHECK(part("Tab Size: 2") != nullptr); + } said = 0; crumbs->setTips({ "line", "problems", "source", "expanded" }); crumbs->showTrailer(doc); - ensure("other tips: said again", said > 2 && part("3 errors")->toolTip == "problems"); + { + INFO("other tips: said again"); + CHECK(said > 2); + CHECK(part("3 errors")->toolTip == "problems"); + } said = 0; Doc& other = tab("b"); crumbs->showTrailer(other); - ensure("another tab: its own", said > 2 && part("3 errors") != nullptr && part("Tab Size: 4") != nullptr); + { + INFO("another tab: its own"); + CHECK(said > 2); + CHECK(part("3 errors") != nullptr); + CHECK(part("Tab Size: 4") != nullptr); + } } - template<> template<> - void alscriptcrumbsbar_object::test<9>() + TEST_CASE_METHOD(alscriptcrumbsbar_data, "alscriptcrumbsbar: what is left from LSL is counted and pressable and not said when none", "[alscriptcrumbsbar]") { - set_test_name("what is left from LSL counted, pressed to show it; none, not said"); ALScriptCrumbsBar* crumbs = bar(); Doc& doc = tab("a"); crumbs->showTrailer(doc); - ensure("none: not said", trailer().find("left from LSL") == std::string::npos); + UNSCOPED_INFO("none: not said"); + CHECK(trailer().find("left from LSL") == std::string::npos); doc.shownMigration = 3; crumbs->showTrailer(doc); const Part* left = part("3 left from LSL"); - ensure("counted", left != nullptr && left->value == "migration" && !left->toolTip.empty()); + { + INFO("counted"); + REQUIRE(left != nullptr); + CHECK(left->value == "migration"); + CHECK_FALSE(left->toolTip.empty()); + } doc.shownMigration = 1; crumbs->showTrailer(doc); - ensure("counted again", part("1 left from LSL") != nullptr); + UNSCOPED_INFO("counted again"); + CHECK(part("1 left from LSL") != nullptr); } - template<> template<> - void alscriptcrumbsbar_object::test<10>() + TEST_CASE_METHOD(alscriptcrumbsbar_data, "alscriptcrumbsbar: the view's tip says why a save preprocesses and warns where it will not", "[alscriptcrumbsbar]") { - set_test_name("the view's tip says why a save preprocesses the script; a preprocessed view of one a save sends as written says so, in the warning's colour"); ALScriptCrumbsBar* crumbs = bar(); Doc& doc = tab("a"); doc.expandedEditor = editor("expanded_a", "integer x;\n"); doc.view = Doc::View::Expanded; told().wanted = ALPreprocessor::Wanted::Directives; crumbs->showTrailer(doc); - ensure("not warned where a save preprocesses it", part("Not preprocessed on save") == nullptr); + UNSCOPED_INFO("not warned where a save preprocesses it"); + CHECK(part("Not preprocessed on save") == nullptr); const Part* view = part("Preprocessed"); - ensure("why, on the view's tip: " + (view ? view->toolTip : std::string()), view && view->toolTip.find("it has directives") != std::string::npos); + { + INFO("why, on the view's tip: " + (view ? view->toolTip : std::string())); + REQUIRE(view); + CHECK(view->toolTip.find("it has directives") != std::string::npos); + } told().wanted = ALPreprocessor::Wanted::Enveloped; crumbs->showTrailer(doc); - ensure("saved with it before", part("Preprocessed") && part("Preprocessed")->toolTip.find("saved with the preprocessor before") != std::string::npos); + { + INFO("saved with it before"); + REQUIRE(part("Preprocessed")); + CHECK(part("Preprocessed")->toolTip.find("saved with the preprocessor before") != std::string::npos); + } told().wanted = ALPreprocessor::Wanted::No; crumbs->showTrailer(doc); const Part* warned = part("Not preprocessed on save"); - ensure("said where a save sends it as written", warned && warned->color == doc.editor->markColor(ALCodeEditor::Mark::Warning) && - warned->toolTip.find("as written") != std::string::npos); + { + INFO("said where a save sends it as written"); + REQUIRE(warned); + CHECK(warned->color == doc.editor->markColor(ALCodeEditor::Mark::Warning)); + CHECK(warned->toolTip.find("as written") != std::string::npos); + } doc.expandedEditor = nullptr; doc.view = Doc::View::Source; crumbs->showTrailer(doc); - ensure("nothing said with no preprocessed view", part("Not preprocessed on save") == nullptr); + UNSCOPED_INFO("nothing said with no preprocessed view"); + CHECK(part("Not preprocessed on save") == nullptr); } } diff --git a/indra/newview/tests/alscriptexplorermodel_test.cpp b/indra/newview/tests/alscriptexplorermodel_test.cpp index 82b3fbbaa6..207b7cced7 100644 --- a/indra/newview/tests/alscriptexplorermodel_test.cpp +++ b/indra/newview/tests/alscriptexplorermodel_test.cpp @@ -26,7 +26,7 @@ #include "../alscriptexplorermodel.h" -#include "../test/lltut.h" +#include #include #include @@ -34,7 +34,7 @@ #include #include -namespace tut +namespace { struct alscriptexplorermodel_data { @@ -210,14 +210,9 @@ namespace tut return found != all.end() ? &*found : nullptr; } }; - typedef test_group alscriptexplorermodel_group; - typedef alscriptexplorermodel_group::object alscriptexplorermodel_object; - tut::alscriptexplorermodel_group alscriptexplorermodel_instance("alscriptexplorermodel"); - template<> template<> - void alscriptexplorermodel_object::test<1>() + TEST_CASE_METHOD(alscriptexplorermodel_data, "alscriptexplorermodel: pins come first then the selection then the open scripts' objects", "[alscriptexplorermodel]") { - set_test_name("the objects in hand, each once: the pinned first, one not in sight by its pin's name, then the selected, then the open scripts' objects"); const LLUUID chair = object(10, "Chair"); const LLUUID table = object(20, "Table", 3); const LLUUID lamp = object(30, "Lamp"); @@ -231,42 +226,52 @@ namespace tut selected = { chair, lamp }; open = { id(21) }; list(); - ensure_equals(names(), std::string("Lamp* Gone(away)* (unnamed)(away)* Chair Table")); - ensure("an object by a prim of it, listed by its root", model.objects().back().root == table && model.objects().back().prims.size() == 3); - ensure("listed prims", model.listed(id(22)) && !model.listed(id(90))); - ensure("what coming or going concerns: a listed prim, a pin away", model.concerns(id(22)) && model.concerns(id(90))); - ensure("not what it neither lists nor asked for", !model.concerns(id(55))); + CHECK(names() == std::string("Lamp* Gone(away)* (unnamed)(away)* Chair Table")); + { + INFO("an object by a prim of it, listed by its root"); + CHECK(model.objects().back().root == table); + CHECK(model.objects().back().prims.size() == 3); + } + CHECK(model.listed(id(22))); + CHECK_FALSE(model.listed(id(90))); + { + INFO("what coming or going concerns: a listed prim, a pin away"); + CHECK(model.concerns(id(22))); + CHECK(model.concerns(id(90))); + } + UNSCOPED_INFO("not what it neither lists nor asked for"); + CHECK_FALSE(model.concerns(id(55))); // Across sessions, the pins as they were read. LLSD kept; model.saveState(kept); - ensure_equals(kept["pinned"].size(), size_t(3)); - ensure_equals(kept["pinned"][1]["name"].asString(), std::string("Gone")); + REQUIRE(kept["pinned"].size() == size_t(3)); + CHECK(kept["pinned"][1]["name"].asString() == std::string("Gone")); } - template<> template<> - void alscriptexplorermodel_object::test<2>() + TEST_CASE_METHOD(alscriptexplorermodel_data, "alscriptexplorermodel: what a prim holds is kept through a relisting and an unfetched answer", "[alscriptexplorermodel]") { - set_test_name("what a prim was known to hold is kept through a listing again, and a listing that did not come leaves it"); const LLUUID chair = object(10, "Chair"); selected = { chair }; list(); - ensure("what a prim holds not known yet, a folder of it may still be opened", !model.rows(std::string()).front().known); + UNSCOPED_INFO("what a prim holds not known yet, a folder of it may still be opened"); + CHECK_FALSE(model.rows(std::string()).front().known); holds(chair, { "sit.lsl", "readme" }); - ensure("known", model.rows(std::string()).front().known); - ensure_equals(rows(), std::string("O:Chair I:sit.lsl I:readme")); + CHECK(model.rows(std::string()).front().known); + CHECK(rows() == std::string("O:Chair I:sit.lsl I:readme")); list(); - ensure_equals("kept through a listing", rows(), std::string("O:Chair I:sit.lsl I:readme")); + UNSCOPED_INFO("kept through a listing"); + CHECK(rows() == std::string("O:Chair I:sit.lsl I:readme")); holds(chair, {}, false); - ensure_equals("no answer is not an empty prim", rows(), std::string("O:Chair I:sit.lsl I:readme")); + UNSCOPED_INFO("no answer is not an empty prim"); + CHECK(rows() == std::string("O:Chair I:sit.lsl I:readme")); holds(chair, { "sit.lsl" }); - ensure_equals("an answer is", rows(), std::string("O:Chair I:sit.lsl")); + UNSCOPED_INFO("an answer is"); + CHECK(rows() == std::string("O:Chair I:sit.lsl")); } - template<> template<> - void alscriptexplorermodel_object::test<3>() + TEST_CASE_METHOD(alscriptexplorermodel_data, "alscriptexplorermodel: two or more empty prims besides the root fold under one row at the end", "[alscriptexplorermodel]") { - set_test_name("a linkset's prims holding nothing go under one row after the rest -- two or more, never the root -- folded until opened, which takes no drop"); const LLUUID house = object(10, "House", 5); selected = { house }; list(); @@ -274,25 +279,34 @@ namespace tut holds(id(11), { "door.lsl" }); holds(id(12), {}); holds(id(13), {}); - ensure_equals("the root, empty, stays first; the two others holding nothing go under one row, folded; one not answered stays", rows(), + UNSCOPED_INFO("the root, empty, stays first; the two others holding nothing go under one row, folded; one not answered stays"); + REQUIRE(rows() == std::string("O:House P:House P:House.1 I:door.lsl P:House.4 E:2+ P:House.2 P:House.3")); const std::vector all = model.rows(std::string()); const Model::Row& group = all[all.size() - 3]; - ensure("the row of them is of no prim to act on or drop into", !Model::Choice::of(group.value) && Model::primOf(group.value).isNull()); - ensure("opened, it asks only to be filled again", model.foldRow(group.value) == Model::Refold::Refill); - ensure_equals(rows(), std::string("O:House P:House P:House.1 I:door.lsl P:House.4 E:2 P:House.2 P:House.3")); - ensure("folded again", model.foldEmpties(house, true) == Model::Refold::Refill && model.foldEmpties(house, true) == Model::Refold::None); + { + INFO("the row of them is of no prim to act on or drop into"); + CHECK_FALSE(Model::Choice::of(group.value)); + CHECK(Model::primOf(group.value).isNull()); + } + UNSCOPED_INFO("opened, it asks only to be filled again"); + CHECK(model.foldRow(group.value) == Model::Refold::Refill); + CHECK(rows() == std::string("O:House P:House P:House.1 I:door.lsl P:House.4 E:2 P:House.2 P:House.3")); + { + INFO("folded again"); + CHECK(model.foldEmpties(house, true) == Model::Refold::Refill); + CHECK(model.foldEmpties(house, true) == Model::Refold::None); + } holds(id(14), { "light.lsl" }); holds(id(13), { "bell.lsl" }); - ensure_equals("one alone holding nothing is not put under a row", rows(), + UNSCOPED_INFO("one alone holding nothing is not put under a row"); + CHECK(rows() == std::string("O:House P:House P:House.1 I:door.lsl P:House.2 P:House.3 I:bell.lsl P:House.4 I:light.lsl")); } - template<> template<> - void alscriptexplorermodel_object::test<4>() + TEST_CASE_METHOD(alscriptexplorermodel_data, "alscriptexplorermodel: a filter shows matches with what holds them and no row of empty prims", "[alscriptexplorermodel]") { - set_test_name("a filter shows an item and what holds it, an object or a prim by its name with all it holds, whatever is folded, and no row of prims holding nothing"); const LLUUID house = object(10, "House", 4); const LLUUID chair = object(20, "Chair"); selected = { house, chair }; @@ -303,53 +317,72 @@ namespace tut holds(id(13), {}); holds(chair, { "sit.lsl" }); model.fold(house, false, true); - ensure_equals("folded, what the object holds is still listed, for the tree to show opened", rows(), + UNSCOPED_INFO("folded, what the object holds is still listed, for the tree to show opened"); + CHECK(rows() == std::string("O:House+ P:House I:main.lsl P:House.1 I:door.lsl I:notes E:2+ P:House.2 P:House.3 O:Chair I:sit.lsl")); - ensure_equals("an item by its name, and what holds it, unfolded", rows("DOOR"), std::string("O:House P:House.1 I:door.lsl")); - ensure_equals("a prim by its name, with all it holds", rows("house.1"), std::string("O:House P:House.1 I:door.lsl I:notes")); - ensure_equals("an object by its name, with every prim, the empty ones by name", rows("house"), + UNSCOPED_INFO("an item by its name, and what holds it, unfolded"); + CHECK(rows("DOOR") == std::string("O:House P:House.1 I:door.lsl")); + UNSCOPED_INFO("a prim by its name, with all it holds"); + CHECK(rows("house.1") == std::string("O:House P:House.1 I:door.lsl I:notes")); + UNSCOPED_INFO("an object by its name, with every prim, the empty ones by name"); + CHECK(rows("house") == std::string("O:House P:House I:main.lsl P:House.1 I:door.lsl I:notes P:House.2 P:House.3")); - ensure_equals("nothing", rows("zzz"), std::string()); + CHECK(rows("zzz") == std::string()); } - template<> template<> - void alscriptexplorermodel_object::test<5>() + TEST_CASE_METHOD(alscriptexplorermodel_data, "alscriptexplorermodel: a large linkset's prims start folded and are asked only when filtered", "[alscriptexplorermodel]") { - set_test_name("a large linkset's prims are folded when first listed, but the root and any with a script open; folded, they are not asked what they hold unless a filter looks"); const size_t count = Model::LARGE_LINKSET + 4; const LLUUID big = object(100, "Big", count); selected = { big }; open = { id(105) }; list(); std::vector asked = ask(false, false); - ensure_equals("the root and the one open", asked.size(), size_t(2)); + UNSCOPED_INFO("the root and the one open"); + REQUIRE(asked.size() == size_t(2)); const std::vector all = model.rows(std::string()); const Model::Row* one = rowNamed(all, "Big.1"); - ensure("a folded prim is listed, folded, what it holds not known", one && one->folded && !one->known); - ensure("the linkset's own row is known: its prims are", all.front().known); - ensure("which", asked[0] == id(100) && asked[1] == id(105)); - ensure("asked, not again until answered", ask(false, false).empty()); - ensure_equals("a filter looks through the rest", ask(false, true).size(), count - 2); - ensure_equals("a refetch asks them all again", ask(true, true).size(), count); + { + INFO("a folded prim is listed, folded, what it holds not known"); + REQUIRE(one); + CHECK(one->folded); + CHECK_FALSE(one->known); + } + UNSCOPED_INFO("the linkset's own row is known: its prims are"); + CHECK(all.front().known); + CHECK(asked[0] == id(100)); + CHECK(asked[1] == id(105)); + UNSCOPED_INFO("asked, not again until answered"); + CHECK(ask(false, false).empty()); + UNSCOPED_INFO("a filter looks through the rest"); + CHECK(ask(false, true).size() == count - 2); + UNSCOPED_INFO("a refetch asks them all again"); + CHECK(ask(true, true).size() == count); holds(id(100), { "core.lsl" }); - ensure("answered and current: not asked", ask(false, false).empty()); + UNSCOPED_INFO("answered and current: not asked"); + CHECK(ask(false, false).empty()); stale.insert(id(100)); std::vector again = ask(false, false); - ensure("answered, but changed since: asked again", again.size() == 1 && again[0] == id(100)); + { + INFO("answered, but changed since: asked again"); + REQUIRE(again.size() == 1); + CHECK(again[0] == id(100)); + } // Opened, a folded prim asks to be listed again, for what it holds. - ensure("unfolded", model.fold(id(101), true, false) == Model::Refold::Relist); + UNSCOPED_INFO("unfolded"); + CHECK(model.fold(id(101), true, false) == Model::Refold::Relist); holds(id(101), {}); list(); - ensure("through a listing again, one opened stays open", model.fold(id(101), true, false) == Model::Refold::None); - ensure("and the rest stay folded", model.fold(id(102), true, true) == Model::Refold::None); + UNSCOPED_INFO("through a listing again, one opened stays open"); + CHECK(model.fold(id(101), true, false) == Model::Refold::None); + UNSCOPED_INFO("and the rest stay folded"); + CHECK(model.fold(id(102), true, true) == Model::Refold::None); } - template<> template<> - void alscriptexplorermodel_object::test<6>() + TEST_CASE_METHOD(alscriptexplorermodel_data, "alscriptexplorermodel: a new item opens once listed and whether a script runs is asked once", "[alscriptexplorermodel]") { - set_test_name("a new item opens once its prim lists it, by id or by name, with its text; and a script not known to run is asked about once"); const LLUUID chair = object(10, "Chair"); selected = { chair }; list(); @@ -359,27 +392,44 @@ namespace tut contents.fetched = true; contents.items.push_back({ id(500), "old.lsl", true, false }); Model::Heard heard = feed(contents); - ensure("not listed yet, nothing opens", heard.listed && heard.opening.empty()); - ensure("the script asked whether it runs", askedRunning == std::vector{ ALScriptRef(chair, id(500)) }); + { + INFO("not listed yet, nothing opens"); + CHECK(heard.listed); + CHECK(heard.opening.empty()); + } + UNSCOPED_INFO("the script asked whether it runs"); + CHECK(askedRunning == std::vector{ ALScriptRef(chair, id(500)) }); index.running(ALScriptRef(chair, id(500)), true); contents.items.push_back({ id(501), "new.lsl", true, false }); heard = feed(contents); - ensure("listed, opened with its text", heard.opening.size() == 1 && heard.opening[0].ref == ALScriptRef(chair, id(501)) && - heard.opening[0].text == std::optional("default {}")); - ensure("the one known to run not asked again", askedRunning.size() == 2 && askedRunning[1].item == id(501)); + { + INFO("listed, opened with its text"); + REQUIRE(heard.opening.size() == 1); + CHECK(heard.opening[0].ref == ALScriptRef(chair, id(501))); + CHECK(heard.opening[0].text == std::optional("default {}")); + } + { + INFO("the one known to run not asked again"); + REQUIRE(askedRunning.size() == 2); + CHECK(askedRunning[1].item == id(501)); + } heard = feed(contents); - ensure("once", heard.opening.empty()); - ensure("a prim not listed is no answer", !model.contents(ALScriptContents{ id(77) }).listed); - ensure("what runs, known", model.knownRunning(ALScriptRef(chair, id(500))) == std::optional(true) && - !model.knownRunning(ALScriptRef(chair, id(501)))); + UNSCOPED_INFO("opened once only"); + CHECK(heard.opening.empty()); + UNSCOPED_INFO("a prim not listed is no answer"); + CHECK_FALSE(model.contents(ALScriptContents{ id(77) }).listed); + { + INFO("what runs, known"); + CHECK(model.knownRunning(ALScriptRef(chair, id(500))) == std::optional(true)); + CHECK_FALSE(model.knownRunning(ALScriptRef(chair, id(501)))); + } model.forgetRunning(); - ensure("let go of for the listed prims, to be asked again", !model.knownRunning(ALScriptRef(chair, id(500)))); + UNSCOPED_INFO("let go of for the listed prims, to be asked again"); + CHECK_FALSE(model.knownRunning(ALScriptRef(chair, id(500)))); } - template<> template<> - void alscriptexplorermodel_object::test<7>() + TEST_CASE_METHOD(alscriptexplorermodel_data, "alscriptexplorermodel: chosen rows reach their prims and scripts and count each script once", "[alscriptexplorermodel]") { - set_test_name("what rows chosen reach: an object every prim, a prim itself; a script with its prim is the queue's; each script counted once"); const LLUUID house = object(10, "House", 3); selected = { house }; list(); @@ -388,47 +438,88 @@ namespace tut holds(id(12), {}); const std::vector all = model.rows(std::string()); Model::Choice whole = *Model::Choice::of(all.front().value); - ensure("an object row", !whole.isItem() && !whole.primRow && whole.prim == house); - ensure_equals("every prim of it", model.containerPrims({ whole }).size(), size_t(3)); - ensure_equals("each script once", model.scriptsReached({ whole }), 3); + { + INFO("an object row"); + CHECK_FALSE(whole.isItem()); + CHECK_FALSE(whole.primRow); + CHECK(whole.prim == house); + } + UNSCOPED_INFO("every prim of it"); + CHECK(model.containerPrims({ whole }).size() == size_t(3)); + UNSCOPED_INFO("each script once"); + CHECK(model.scriptsReached({ whole }) == 3); Model::Choice prim = *Model::Choice::of(rowNamed(all, "House.1")->value); Model::Choice c = *Model::Choice::of(rowNamed(all, "c.lsl")->value); Model::Choice a = *Model::Choice::of(rowNamed(all, "a.lsl")->value); - ensure("a prim row", prim.primRow && prim.prim == id(11)); + { + INFO("a prim row"); + CHECK(prim.primRow); + CHECK(prim.prim == id(11)); + } const auto prims = model.containerPrims({ prim, c, a }); - ensure("the prim itself", prims.size() == 1 && prims[0].first == id(11) && prims[0].second == "House.1"); - ensure("its script is the queue's", Model::walkedByQueue(c, prims) && !Model::walkedByQueue(a, prims)); - ensure_equals("counted once", model.scriptsReached({ prim, c, a }), 2); - ensure("a notecard chosen reaches no script", model.scriptsReached({ *Model::Choice::of(rowNamed(all, "notes")->value) }) == 0); + { + INFO("the prim itself"); + REQUIRE(prims.size() == 1); + CHECK(prims[0].first == id(11)); + CHECK(prims[0].second == "House.1"); + } + { + INFO("its script is the queue's"); + CHECK(Model::walkedByQueue(c, prims)); + CHECK_FALSE(Model::walkedByQueue(a, prims)); + } + UNSCOPED_INFO("counted once"); + CHECK(model.scriptsReached({ prim, c, a }) == 2); + UNSCOPED_INFO("a notecard chosen reaches no script"); + CHECK(model.scriptsReached({ *Model::Choice::of(rowNamed(all, "notes")->value) }) == 0); } - template<> template<> - void alscriptexplorermodel_object::test<8>() + TEST_CASE_METHOD(alscriptexplorermodel_data, "alscriptexplorermodel: a rename shows at once and a root's new name reaches its object's pin", "[alscriptexplorermodel]") { - set_test_name("a prim renamed here is called so at once, the root its object and its pin too; names heard change them the same way"); const LLUUID house = object(10, "House", 2); selected = { house }; model.togglePinned(house, "House"); list(); model.renamed(id(11), "Door"); - ensure_equals(model.objects().front().prims[1].name, std::string("Door")); - ensure("a prim is not its object", model.objects().front().name == "House" && !model.takePinsChanged()); + CHECK(model.objects().front().prims[1].name == std::string("Door")); + { + INFO("a prim is not its object"); + CHECK(model.objects().front().name == "House"); + CHECK_FALSE(model.takePinsChanged()); + } model.renamed(house, "Home"); - ensure("the root is", model.objects().front().name == "Home" && model.takePinsChanged() && model.pins().front().name == "Home"); - ensure("told once", !model.takePinsChanged()); - ensure("heard as it is, nothing changes", !model.rereadNames([](const LLUUID& prim) { return prim == alscriptexplorermodel_data::id(10) ? std::string("Home") : std::string(); })); - ensure("heard otherwise, it does", model.rereadNames([](const LLUUID& prim) { return prim == alscriptexplorermodel_data::id(10) ? std::string("Villa") : std::string(); })); - ensure("the pin with it", model.nameOf(house) == "Villa" && model.takePinsChanged() && model.pins().front().name == "Villa"); - ensure("a name asked once while listed", model.askName(id(11)) && !model.askName(id(11)) && !model.askName(id(99))); + { + INFO("the root is"); + CHECK(model.objects().front().name == "Home"); + CHECK(model.takePinsChanged()); + CHECK(model.pins().front().name == "Home"); + } + UNSCOPED_INFO("told once"); + CHECK_FALSE(model.takePinsChanged()); + UNSCOPED_INFO("heard as it is, nothing changes"); + CHECK_FALSE(model.rereadNames([](const LLUUID& prim) { return prim == alscriptexplorermodel_data::id(10) ? std::string("Home") : std::string(); })); + UNSCOPED_INFO("heard otherwise, it does"); + CHECK(model.rereadNames([](const LLUUID& prim) { return prim == alscriptexplorermodel_data::id(10) ? std::string("Villa") : std::string(); })); + { + INFO("the pin with it"); + CHECK(model.nameOf(house) == "Villa"); + CHECK(model.takePinsChanged()); + CHECK(model.pins().front().name == "Villa"); + } + { + INFO("a name asked once while listed"); + CHECK(model.askName(id(11))); + CHECK_FALSE(model.askName(id(11))); + CHECK_FALSE(model.askName(id(99))); + } model.renamed(id(11), "Gate"); - ensure("renamed, asked again", model.askName(id(11))); + UNSCOPED_INFO("renamed, asked again"); + CHECK(model.askName(id(11))); } - template<> template<> - void alscriptexplorermodel_object::test<9>() + TEST_CASE_METHOD(alscriptexplorermodel_data, "alscriptexplorermodel: folding objects and revealing prims and pinning from the rows", "[alscriptexplorermodel]") { - set_test_name("an object folded is a folded row, opened asks to be listed again, and a prim revealed unfolds what holds it; pins from the rows"); const LLUUID house = object(10, "House", 2); const LLUUID chair = object(20, "Chair"); selected = { house, chair }; @@ -436,78 +527,114 @@ namespace tut holds(id(10), { "a.lsl" }); holds(id(11), { "b.lsl" }); holds(chair, {}); - ensure("folded", model.fold(house, false) == Model::Refold::Refill && model.folded(house)); - ensure_equals(rows(), std::string("O:House+ P:House I:a.lsl P:House.1 I:b.lsl O:Chair")); - ensure("a prim revealed unfolds what holds it, and was asked what it holds", !model.unfoldTo(id(11)) && !model.folded(house)); - ensure("opened", model.fold(house, false, false) == Model::Refold::None); + CHECK(model.fold(house, false) == Model::Refold::Refill); + CHECK(model.folded(house)); + CHECK(rows() == std::string("O:House+ P:House I:a.lsl P:House.1 I:b.lsl O:Chair")); + { + INFO("a prim revealed unfolds what holds it, and was asked what it holds"); + CHECK_FALSE(model.unfoldTo(id(11))); + CHECK_FALSE(model.folded(house)); + } + UNSCOPED_INFO("opened"); + CHECK(model.fold(house, false, false) == Model::Refold::None); model.fold(house, false, true); - ensure("opened again asks to be listed", model.fold(house, false) == Model::Refold::Relist); + UNSCOPED_INFO("opened again asks to be listed"); + CHECK(model.fold(house, false) == Model::Refold::Relist); // Pinned from the rows: every object among them, as the first is not. const std::vector all = model.rows(std::string()); model.pin({ *Model::Choice::of(all[0].value), *Model::Choice::of(rowNamed(all, "b.lsl")->value), *Model::Choice::of(rowNamed(all, "Chair")->value) }); - ensure("both pinned, each once", model.pins().size() == 2 && model.isPinned(house) && model.isPinned(chair)); + { + INFO("both pinned, each once"); + CHECK(model.pins().size() == 2); + CHECK(model.isPinned(house)); + CHECK(model.isPinned(chair)); + } model.pin({ *Model::Choice::of(rowNamed(all, "Chair")->value) }); - ensure("let go", model.pins().size() == 1 && !model.isPinned(chair)); + { + INFO("let go"); + CHECK(model.pins().size() == 1); + CHECK_FALSE(model.isPinned(chair)); + } } - template<> template<> - void alscriptexplorermodel_object::test<10>() + TEST_CASE_METHOD(alscriptexplorermodel_data, "alscriptexplorermodel: a prim dropped into is asked again of the region until let go of", "[alscriptexplorermodel]") { - set_test_name("a prim dropped into, whose object keeps its copy of what it held, is asked again, and of the region, until let go of"); const LLUUID house = object(10, "House", 2); selected = { house }; list(); - ensure_equals("both asked first", ask(false, false).size(), size_t(2)); + UNSCOPED_INFO("both asked first"); + CHECK(ask(false, false).size() == size_t(2)); holds(id(10), { "a.lsl" }); holds(id(11), {}); - ensure("known and current: nothing asked", ask(false, false).empty()); + UNSCOPED_INFO("known and current: nothing asked"); + CHECK(ask(false, false).empty()); model.askRegion(id(11)); - ensure("the one dropped into, though its object says current", ask(false, false) == std::vector({ id(11) })); - ensure("and of the region", model.asksRegion(id(11)) && !model.asksRegion(id(10)) && asks.back().second); + UNSCOPED_INFO("the one dropped into, though its object says current"); + CHECK(ask(false, false) == std::vector({ id(11) })); + { + INFO("and of the region"); + CHECK(model.asksRegion(id(11))); + CHECK_FALSE(model.asksRegion(id(10))); + CHECK(asks.back().second); + } holds(id(11), { "b.lsl" }); - ensure("still, for the ask a moment later", ask(false, false) == std::vector({ id(11) })); + UNSCOPED_INFO("still, for the ask a moment later"); + CHECK(ask(false, false) == std::vector({ id(11) })); holds(id(11), { "b.lsl" }); model.doneAskingRegion(); - ensure("let go of: as any other", ask(false, false).empty() && !model.asksRegion(id(11))); + { + INFO("let go of: as any other"); + CHECK(ask(false, false).empty()); + CHECK_FALSE(model.asksRegion(id(11))); + } } - template<> template<> - void alscriptexplorermodel_object::test<11>() + TEST_CASE_METHOD(alscriptexplorermodel_data, "alscriptexplorermodel: folds of an object no longer listed reset and prims are found by id", "[alscriptexplorermodel]") { - set_test_name("what was folded of an object no longer listed is let go of, and folded as at first should it come back; prims found by id"); const size_t count = Model::LARGE_LINKSET + 2; const LLUUID big = object(200, "Big", count); selected = { big }; list(); - ensure("a prim of a large linkset folded at first", model.fold(id(201), true, true) == Model::Refold::None); - ensure("opened", model.fold(id(201), true, false) == Model::Refold::Relist); - ensure("folded shut", model.fold(big, false, true) == Model::Refold::Refill); + UNSCOPED_INFO("a prim of a large linkset folded at first"); + CHECK(model.fold(id(201), true, true) == Model::Refold::None); + UNSCOPED_INFO("opened"); + CHECK(model.fold(id(201), true, false) == Model::Refold::Relist); + UNSCOPED_INFO("folded shut"); + CHECK(model.fold(big, false, true) == Model::Refold::Refill); list(); - ensure("open through a listing again", model.fold(id(201), true, false) == Model::Refold::None); - ensure("and the object shut", model.folded(big)); + UNSCOPED_INFO("open through a listing again"); + CHECK(model.fold(id(201), true, false) == Model::Refold::None); + UNSCOPED_INFO("and the object shut"); + CHECK(model.folded(big)); selected.clear(); list(); - ensure("not listed: nothing folded", !model.folded(big)); + UNSCOPED_INFO("not listed: nothing folded"); + CHECK_FALSE(model.folded(big)); selected = { big }; list(); - ensure("back: its prims folded as at first", model.fold(id(201), true, true) == Model::Refold::None); - ensure("the object open", !model.folded(big)); + UNSCOPED_INFO("back: its prims folded as at first"); + CHECK(model.fold(id(201), true, true) == Model::Refold::None); + UNSCOPED_INFO("the object open"); + CHECK_FALSE(model.folded(big)); // By id, wherever it is in the list. - ensure("a prim revealed, never asked what it holds, is to be asked", model.unfoldTo(id(205))); - ensure("unfolded to", model.fold(id(205), true, false) == Model::Refold::None); + UNSCOPED_INFO("a prim revealed, never asked what it holds, is to be asked"); + CHECK(model.unfoldTo(id(205))); + UNSCOPED_INFO("unfolded to"); + CHECK(model.fold(id(205), true, false) == Model::Refold::None); model.renamed(id(203), "Middle"); - ensure("renamed in place", rowNamed(model.rows(std::string()), "Middle") != nullptr); - ensure_equals("named by its root", model.nameOf(big), std::string("Big")); - ensure("present", model.present(big) && !model.present(id(203))); + UNSCOPED_INFO("renamed in place"); + CHECK(rowNamed(model.rows(std::string()), "Middle") != nullptr); + UNSCOPED_INFO("named by its root"); + CHECK(model.nameOf(big) == std::string("Big")); + CHECK(model.present(big)); + CHECK_FALSE(model.present(id(203))); } - template<> template<> - void alscriptexplorermodel_object::test<12>() + TEST_CASE_METHOD(alscriptexplorermodel_data, "alscriptexplorermodel: link numbers and the permissions of items and prims", "[alscriptexplorermodel]") { - set_test_name("a prim by its link number, an item by what the agent may not do with it, a prim by whether it may be changed"); const LLUUID house = object(10, "House", 3); world[house].prims[2].modify = false; const LLUUID lone = object(40, "Lone"); @@ -527,22 +654,45 @@ namespace tut const std::vector all = model.rows(std::string()); const Model::Row* hinge = rowNamed(all, "House.1"); - ensure("the second prim is link 2", hinge && hinge->kind == Model::Row::Kind::Prim && hinge->link == 2); + { + INFO("the second prim is link 2"); + REQUIRE(hinge); + CHECK(hinge->kind == Model::Row::Kind::Prim); + CHECK(hinge->link == 2); + } const Model::Row* latch = rowNamed(all, "House.2"); - ensure("the third, 3", latch && latch->link == 3); - ensure_equals("the root is link 1", model.linkNumber(house), 1); - ensure_equals("a prim alone is 0", model.linkNumber(lone), 0); + { + INFO("the third, 3"); + REQUIRE(latch); + CHECK(latch->link == 3); + } + UNSCOPED_INFO("the root is link 1"); + CHECK(model.linkNumber(house) == 1); + UNSCOPED_INFO("a prim alone is 0"); + CHECK(model.linkNumber(lone) == 0); const Model::Row* item = rowNamed(all, "locked.lsl"); - ensure("no modify, no copy", item && item->noModify && item->noCopy); - ensure("found by its ref", model.itemAt(ALScriptRef(id(11), id(2000))).has_value() && !model.itemAt(ALScriptRef(id(11), id(2000)))->modify); - ensure("a prim the agent may not change", !model.primModifiable(id(12)) && model.primModifiable(id(11))); - ensure("one not seen, yes", model.primModifiable(id(99))); + { + INFO("no modify, no copy"); + REQUIRE(item); + CHECK(item->noModify); + CHECK(item->noCopy); + } + { + INFO("found by its ref"); + REQUIRE(model.itemAt(ALScriptRef(id(11), id(2000))).has_value()); + CHECK_FALSE(model.itemAt(ALScriptRef(id(11), id(2000)))->modify); + } + { + INFO("a prim the agent may not change"); + CHECK_FALSE(model.primModifiable(id(12))); + CHECK(model.primModifiable(id(11))); + } + UNSCOPED_INFO("one not seen, yes"); + CHECK(model.primModifiable(id(99))); } - template<> template<> - void alscriptexplorermodel_object::test<13>() + TEST_CASE_METHOD(alscriptexplorermodel_data, "alscriptexplorermodel: a pin keeps its object's last place and region across sessions", "[alscriptexplorermodel]") { - set_test_name("a pin keeps the place its object was last seen at, and the region's name, across sessions, for one away"); const LLUUID house = object(10, "House"); world[house].position = LLVector3d(256000.0 + 10.0, 256000.0 + 20.0, 30.0); world[house].region = "Ahern"; @@ -553,23 +703,42 @@ namespace tut rows[0].prim = house; model.pin(rows); const Model::Pin* pin = model.pinOf(house); - ensure("pinned with its place", pin && pin->position && pin->position->mdV[VZ] == 30.0 && pin->region == "Ahern"); + { + INFO("pinned with its place"); + REQUIRE(pin); + REQUIRE(pin->position); + CHECK(pin->position->mdV[VZ] == 30.0); + CHECK(pin->region == "Ahern"); + } // Moved a little: kept as it was; far, or to another region: kept anew. world[house].position = LLVector3d(256000.0 + 12.0, 256000.0 + 20.0, 30.0); list(); - ensure("a step is no new place", !model.takePinsChanged() && model.pinOf(house)->position->mdV[VX] == 256010.0); + { + INFO("a step is no new place"); + CHECK_FALSE(model.takePinsChanged()); + CHECK(model.pinOf(house)->position->mdV[VX] == 256010.0); + } world[house].position = LLVector3d(256000.0 + 100.0, 256000.0 + 20.0, 30.0); world[house].region = "Morris"; list(); - ensure("far, and elsewhere: kept, and said", model.takePinsChanged() && model.pinOf(house)->region == "Morris"); + { + INFO("far, and elsewhere: kept, and said"); + CHECK(model.takePinsChanged()); + CHECK(model.pinOf(house)->region == "Morris"); + } // Across sessions, and away. LLSD state; model.saveState(state); Model again(index); again.readState(state); - ensure("read back", again.pinOf(house) && again.pinOf(house)->position && again.pinOf(house)->region == "Morris"); + { + INFO("read back"); + REQUIRE(again.pinOf(house)); + CHECK(again.pinOf(house)->position); + CHECK(again.pinOf(house)->region == "Morris"); + } world.clear(); selected.clear(); Model::Listing listing; @@ -577,13 +746,16 @@ namespace tut listing.unnamed = "(unnamed)"; again.list(listing); const std::vector all = again.rows(std::string()); - ensure("listed away, with its region", !all.empty() && !all[0].present && all[0].region == "Morris"); + { + INFO("listed away, with its region"); + REQUIRE_FALSE(all.empty()); + CHECK_FALSE(all[0].present); + CHECK(all[0].region == "Morris"); + } } - template<> template<> - void alscriptexplorermodel_object::test<14>() + TEST_CASE_METHOD(alscriptexplorermodel_data, "alscriptexplorermodel: two items of one kind compare and the candidates list same names first", "[alscriptexplorermodel]") { - set_test_name("two scripts or two notecards compare; what one compares with is its kind elsewhere, its name first; and where each is"); const LLUUID door = object(10, "Door", 2); const LLUUID lamp = object(20, "Lamp"); selected = { door, lamp }; @@ -626,12 +798,22 @@ namespace tut prim.prim = id(11); const auto pair = Model::comparing({ here, there }); - ensure("two scripts, in their order", pair && pair->first.ref() == here.ref() && pair->second.ref() == there.ref()); - ensure("not a script and a notecard", !Model::comparing({ here, notes })); - ensure("not one", !Model::comparing({ here })); - ensure("not three", !Model::comparing({ here, there, blink })); - ensure("not a prim", !Model::comparing({ here, prim })); - ensure("not the same twice", !Model::comparing({ here, here })); + { + INFO("two scripts, in their order"); + REQUIRE(pair); + CHECK(pair->first.ref() == here.ref()); + CHECK(pair->second.ref() == there.ref()); + } + UNSCOPED_INFO("not a script and a notecard"); + CHECK_FALSE(Model::comparing({ here, notes })); + UNSCOPED_INFO("not one"); + CHECK_FALSE(Model::comparing({ here })); + UNSCOPED_INFO("not three"); + CHECK_FALSE(Model::comparing({ here, there, blink })); + UNSCOPED_INFO("not a prim"); + CHECK_FALSE(Model::comparing({ here, prim })); + UNSCOPED_INFO("not the same twice"); + CHECK_FALSE(Model::comparing({ here, here })); const std::vector others = model.comparableWith(here); std::string said; @@ -639,11 +821,16 @@ namespace tut { said += (said.empty() ? "" : " ") + model.placeOf(one) + "/" + one.name; } - ensure_equals("scripts only, its name first, not itself", said, + UNSCOPED_INFO("scripts only, its name first, not itself"); + CHECK(said == "Door" + std::string(Model::PLACE_SEPARATOR) + "Door.1/open.lsl Lamp/open.lsl Lamp/blink.lsl"); - ensure_equals("a notecard's are notecards", model.comparableWith(notes).size(), 1U); - ensure("a prim compares with nothing", model.comparableWith(prim).empty()); - ensure_equals("a root's place is its object", model.placeOf(here), std::string("Door")); - ensure_equals("a child's has its prim", model.placeOf(child), "Door" + std::string(Model::PLACE_SEPARATOR) + "Door.1"); + UNSCOPED_INFO("a notecard's are notecards"); + CHECK(model.comparableWith(notes).size() == 1U); + UNSCOPED_INFO("a prim compares with nothing"); + CHECK(model.comparableWith(prim).empty()); + UNSCOPED_INFO("a root's place is its object"); + CHECK(model.placeOf(here) == std::string("Door")); + UNSCOPED_INFO("a child's has its prim"); + CHECK(model.placeOf(child) == "Door" + std::string(Model::PLACE_SEPARATOR) + "Door.1"); } } diff --git a/indra/newview/tests/alscriptexplorertree_test.cpp b/indra/newview/tests/alscriptexplorertree_test.cpp index 0af6561178..48a91b4445 100644 --- a/indra/newview/tests/alscriptexplorertree_test.cpp +++ b/indra/newview/tests/alscriptexplorertree_test.cpp @@ -33,7 +33,7 @@ #include "../../llui/tests/alheadlessui_fixture.h" -#include "../test/lltut.h" +#include #include #include @@ -46,7 +46,7 @@ void LLOpenFilteredFolders::doItem(LLFolderViewItem* item) {} void LLSelectFirstFilteredItem::doFolder(LLFolderViewFolder* folder) {} void LLSelectFirstFilteredItem::doItem(LLFolderViewItem* item) {} -namespace tut +namespace { struct alscriptexplorertree_data { @@ -78,7 +78,7 @@ namespace tut { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } LLPanel::Params p(LLUICtrlFactory::getDefaultParams()); p.name = "explorer_tree"; @@ -171,14 +171,9 @@ namespace tut return LLSD(); } }; - typedef test_group alscriptexplorertree_group; - typedef alscriptexplorertree_group::object alscriptexplorertree_object; - tut::alscriptexplorertree_group alscriptexplorertree_instance("alscriptexplorertree"); - template<> template<> - void alscriptexplorertree_object::test<1>() + TEST_CASE_METHOD(alscriptexplorertree_data, "alscriptexplorertree: rows nest by object and prim and empty prims gather under their own row", "[alscriptexplorertree]") { - set_test_name("rows become a tree: an object at the top, a linkset's prims in it, those holding nothing under a row of their own, and each item in its prim, or in an object of one prim"); ALScriptExplorerTree& t = make(); const LLUUID house = object(10, "House", 3); const LLUUID chair = object(20, "Chair"); @@ -189,23 +184,42 @@ namespace tut holds(id(12), {}); holds(chair, { "sit.lsl", "notes" }); show(); - ensure("the objects at the top", t.parentOf(objectRow(house)).isUndefined() && t.parentOf(objectRow(chair)).isUndefined()); - ensure("a prim in its object", llsd_equals(t.parentOf(primRow(house, id(10))), objectRow(house))); - ensure("an item in its prim", llsd_equals(t.parentOf(itemRow("a.lsl")), primRow(house, id(10)))); - ensure("the prims holding nothing under their own row", llsd_equals(t.parentOf(primRow(house, id(11))), emptiesRow(house)) && - llsd_equals(t.parentOf(primRow(house, id(12))), emptiesRow(house))); - ensure("in an object of one prim, the object holds them", llsd_equals(t.parentOf(itemRow("sit.lsl")), objectRow(chair)) && - t.has(itemRow("notes"))); - ensure("each as the owner said", t.label(primRow(house, id(10))) == "House" && t.suffix(itemRow("a.lsl")) == " (running)" && - t.suffix(itemRow("notes")).empty()); - ensure("open as the rows say: the object open, the prims holding nothing folded", t.isOpen(objectRow(house)) && !t.isOpen(emptiesRow(house))); - ensure("and nothing told of it: it was the rows' doing", folds.empty()); + { + INFO("the objects at the top"); + REQUIRE(t.parentOf(objectRow(house)).isUndefined()); + REQUIRE(t.parentOf(objectRow(chair)).isUndefined()); + } + UNSCOPED_INFO("a prim in its object"); + CHECK(llsd_equals(t.parentOf(primRow(house, id(10))), objectRow(house))); + UNSCOPED_INFO("an item in its prim"); + CHECK(llsd_equals(t.parentOf(itemRow("a.lsl")), primRow(house, id(10)))); + { + INFO("the prims holding nothing under their own row"); + CHECK(llsd_equals(t.parentOf(primRow(house, id(11))), emptiesRow(house))); + CHECK(llsd_equals(t.parentOf(primRow(house, id(12))), emptiesRow(house))); + } + { + INFO("in an object of one prim, the object holds them"); + CHECK(llsd_equals(t.parentOf(itemRow("sit.lsl")), objectRow(chair))); + REQUIRE(t.has(itemRow("notes"))); + } + { + INFO("each as the owner said"); + CHECK(t.label(primRow(house, id(10))) == "House"); + CHECK(t.suffix(itemRow("a.lsl")) == " (running)"); + REQUIRE(t.suffix(itemRow("notes")).empty()); + } + { + INFO("open as the rows say: the object open, the prims holding nothing folded"); + CHECK(t.isOpen(objectRow(house))); + CHECK_FALSE(t.isOpen(emptiesRow(house))); + } + UNSCOPED_INFO("and nothing told of it: it was the rows' doing"); + REQUIRE(folds.empty()); } - template<> template<> - void alscriptexplorertree_object::test<2>() + TEST_CASE_METHOD(alscriptexplorertree_data, "alscriptexplorertree: showing again updates rows in place and drops the ones gone", "[alscriptexplorertree]") { - set_test_name("told again, a row is kept -- what is chosen stays chosen -- and changed in place; a row gone goes with what it held; a prim no longer holding nothing moves out from under that row"); ALScriptExplorerTree& t = make(); const LLUUID house = object(10, "House", 3); const LLUUID chair = object(20, "Chair"); @@ -216,30 +230,46 @@ namespace tut holds(id(12), {}); holds(chair, { "sit.lsl" }); show(); - ensure("chosen", t.choose(itemRow("a.lsl"), false) && t.chosen().size() == 1); + CHECK(t.choose(itemRow("a.lsl"), false)); + REQUIRE(t.chosen().size() == 1); state = "stopped"; show(); - ensure("still chosen, told again", t.chosen().size() == 1 && llsd_equals(t.chosen().front(), itemRow("a.lsl"))); - ensure("its state changed in place", t.suffix(itemRow("a.lsl")) == " (stopped)"); + { + INFO("still chosen, told again"); + REQUIRE(t.chosen().size() == 1); + CHECK(llsd_equals(t.chosen().front(), itemRow("a.lsl"))); + } + UNSCOPED_INFO("its state changed in place"); + CHECK(t.suffix(itemRow("a.lsl")) == " (stopped)"); holds(id(11), { "b.lsl" }); show(); - ensure("one alone holding nothing: no row for them", !t.has(emptiesRow(house))); - ensure("both prims in the object again", llsd_equals(t.parentOf(primRow(house, id(11))), objectRow(house)) && - llsd_equals(t.parentOf(primRow(house, id(12))), objectRow(house))); - ensure("and what they hold in them", llsd_equals(t.parentOf(itemRow("b.lsl")), primRow(house, id(11)))); + UNSCOPED_INFO("one alone holding nothing: no row for them"); + REQUIRE_FALSE(t.has(emptiesRow(house))); + { + INFO("both prims in the object again"); + CHECK(llsd_equals(t.parentOf(primRow(house, id(11))), objectRow(house))); + CHECK(llsd_equals(t.parentOf(primRow(house, id(12))), objectRow(house))); + } + UNSCOPED_INFO("and what they hold in them"); + CHECK(llsd_equals(t.parentOf(itemRow("b.lsl")), primRow(house, id(11)))); selected = { chair }; show(); - ensure("an object gone, with all it held", !t.has(objectRow(house)) && !t.has(primRow(house, id(10))) && !t.has(itemRow("a.lsl"))); - ensure("what was chosen in it let go of", t.chosen().empty()); - ensure("the rest kept", t.has(itemRow("sit.lsl"))); + { + INFO("an object gone, with all it held"); + REQUIRE_FALSE(t.has(objectRow(house))); + REQUIRE_FALSE(t.has(primRow(house, id(10)))); + REQUIRE_FALSE(t.has(itemRow("a.lsl"))); + } + UNSCOPED_INFO("what was chosen in it let go of"); + REQUIRE(t.chosen().empty()); + UNSCOPED_INFO("the rest kept"); + REQUIRE(t.has(itemRow("sit.lsl"))); } - template<> template<> - void alscriptexplorertree_object::test<3>() + TEST_CASE_METHOD(alscriptexplorertree_data, "alscriptexplorertree: a person's fold is told and kept and the rows' folds are not told", "[alscriptexplorertree]") { - set_test_name("a folder folded by a person tells the owner, and stays folded while the rows say nothing new of it; the rows folding or opening it tell nothing"); ALScriptExplorerTree& t = make(); const LLUUID house = object(10, "House", 2); selected = { house }; @@ -248,22 +278,34 @@ namespace tut holds(id(11), { "b.lsl" }); show(); t.toggle(objectRow(house)); - ensure("folded, and told", !t.isOpen(objectRow(house)) && folds.size() == 1 && folds[0].second); + { + INFO("folded, and told"); + CHECK_FALSE(t.isOpen(objectRow(house))); + REQUIRE(folds.size() == 1); + CHECK(folds[0].second); + } show("b"); - ensure("a filter's rows say it open, as they did: it stays as the person left it", !t.isOpen(objectRow(house))); + UNSCOPED_INFO("a filter's rows say it open, as they did: it stays as the person left it"); + CHECK_FALSE(t.isOpen(objectRow(house))); show(); model.fold(house, false, true); show(); - ensure("the rows fold it: nothing told", !t.isOpen(objectRow(house)) && folds.size() == 1); + { + INFO("the rows fold it: nothing told"); + CHECK_FALSE(t.isOpen(objectRow(house))); + REQUIRE(folds.size() == 1); + } model.fold(house, false, false); show(); - ensure("and open it", t.isOpen(objectRow(house)) && folds.size() == 1); + { + INFO("and open it"); + CHECK(t.isOpen(objectRow(house))); + REQUIRE(folds.size() == 1); + } } - template<> template<> - void alscriptexplorertree_object::test<4>() + TEST_CASE_METHOD(alscriptexplorertree_data, "alscriptexplorertree: choosing a shown row opens what holds it and tells the owner", "[alscriptexplorertree]") { - set_test_name("a row chosen opens what holds it, as a person would, which the owner is told of; one not shown is not chosen"); ALScriptExplorerTree& t = make(); const LLUUID house = object(10, "House", 2); selected = { house }; @@ -272,20 +314,28 @@ namespace tut holds(id(11), { "b.lsl" }); model.fold(id(11), true, true); show(); - ensure("folded as the rows say", !t.isOpen(primRow(house, id(11)))); - ensure("chosen", t.choose(itemRow("b.lsl"), false)); - ensure("what holds it opened", t.isOpen(primRow(house, id(11)))); - ensure("and the owner told, to ask what it holds", folds.size() == 1 && !folds[0].second && - llsd_equals(folds[0].first, primRow(house, id(11)))); - ensure("nothing chosen that is not a row", !t.choose(itemRow("nothing"), false) && t.chosen().size() == 1); + UNSCOPED_INFO("folded as the rows say"); + CHECK_FALSE(t.isOpen(primRow(house, id(11)))); + CHECK(t.choose(itemRow("b.lsl"), false)); + UNSCOPED_INFO("what holds it opened"); + CHECK(t.isOpen(primRow(house, id(11)))); + { + INFO("and the owner told, to ask what it holds"); + REQUIRE(folds.size() == 1); + CHECK_FALSE(folds[0].second); + CHECK(llsd_equals(folds[0].first, primRow(house, id(11)))); + } + { + INFO("nothing chosen that is not a row"); + CHECK_FALSE(t.choose(itemRow("nothing"), false)); + REQUIRE(t.chosen().size() == 1); + } t.chooseNone(); - ensure("none", t.chosen().empty()); + REQUIRE(t.chosen().empty()); } - template<> template<> - void alscriptexplorertree_object::test<5>() + TEST_CASE_METHOD(alscriptexplorertree_data, "alscriptexplorertree: keyboard focus goes to the folder view and escape passes through", "[alscriptexplorertree]") { - set_test_name("the keyboard given to the tree is where the folder view hears it, not the panel; and escape goes on past the panel, not taken to leave nothing with the keyboard"); ALScriptExplorerTree& t = make(); const LLUUID house = object(10, "House", 2); selected = { house }; @@ -293,22 +343,29 @@ namespace tut holds(id(10), { "a.lsl" }); holds(id(11), { "b.lsl" }); show(); - ensure("chosen, with the keyboard", t.choose(objectRow(house), true)); + UNSCOPED_INFO("chosen, with the keyboard"); + CHECK(t.choose(objectRow(house), true)); LLView* focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); - ensure("in the tree, not the panel itself", focus && focus != &t && t.hasFocus()); + { + INFO("in the tree, not the panel itself"); + REQUIRE(focus); + CHECK(focus != &t); + CHECK(t.hasFocus()); + } // What the folder view does with the arrows goes by its layout, which // a test without the viewer's fonts cannot make; F2 it takes whatever // is chosen, which says the keys reach it. - ensure("F2 is the folder view's", focus->handleKey(KEY_F2, MASK_NONE, false)); - ensure("escape not taken", !focus->handleKey(KEY_ESCAPE, MASK_NONE, false)); - ensure("the keyboard still there", t.hasFocus()); + UNSCOPED_INFO("F2 is the folder view's"); + CHECK(focus->handleKey(KEY_F2, MASK_NONE, false)); + UNSCOPED_INFO("escape not taken"); + CHECK_FALSE(focus->handleKey(KEY_ESCAPE, MASK_NONE, false)); + UNSCOPED_INFO("the keyboard still there"); + CHECK(t.hasFocus()); gFocusMgr.setKeyboardFocus(nullptr); } - template<> template<> - void alscriptexplorertree_object::test<6>() + TEST_CASE_METHOD(alscriptexplorertree_data, "alscriptexplorertree: only an item may be dragged out and the owner starts the drag", "[alscriptexplorertree]") { - set_test_name("an item may be dragged out, which the owner starts; a prim or an object may not"); ALScriptExplorerTree& t = make(); const LLUUID house = object(10, "House"); selected = { house }; @@ -324,12 +381,21 @@ namespace tut t.setHooks(std::move(hooks)); // The folder view drags only from a row that says it may move; the // press and the move themselves want a window this has none of. - ensure("chosen", t.choose(itemRow("a.lsl"), false)); + CHECK(t.choose(itemRow("a.lsl"), false)); LLFolderViewItem* item = t.folderView()->getCurSelectedItem(); - ensure("an item may move", item && item->isMovable()); - ensure("and the drag is the owner's", t.folderView()->startDrag() && drags == 1); - ensure("chosen", t.choose(objectRow(house), false)); - ensure("an object may not", !t.folderView()->getCurSelectedItem()->isMovable()); + { + INFO("an item may move"); + REQUIRE(item); + CHECK(item->isMovable()); + } + { + INFO("and the drag is the owner's"); + CHECK(t.folderView()->startDrag()); + CHECK(drags == 1); + } + CHECK(t.choose(objectRow(house), false)); + UNSCOPED_INFO("an object may not"); + CHECK_FALSE(t.folderView()->getCurSelectedItem()->isMovable()); gFocusMgr.setKeyboardFocus(nullptr); } } diff --git a/indra/newview/tests/alscriptexternaleditor_test.cpp b/indra/newview/tests/alscriptexternaleditor_test.cpp index 794e723c4c..5803af8385 100644 --- a/indra/newview/tests/alscriptexternaleditor_test.cpp +++ b/indra/newview/tests/alscriptexternaleditor_test.cpp @@ -32,7 +32,7 @@ #include "fsyspath.h" -#include "../test/lltut.h" +#include #include #include @@ -88,7 +88,7 @@ namespace }; } -namespace tut +namespace { struct alscriptexternaleditor_data { @@ -117,7 +117,7 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } files = std::make_unique(services); unit = std::make_unique(services, studio, studio, files->unit, studio); @@ -154,81 +154,99 @@ namespace tut } }; - typedef test_group alscriptexternaleditor_group; - typedef alscriptexternaleditor_group::object alscriptexternaleditor_object; - alscriptexternaleditor_group alscriptexternaleditor_instance("alscriptexternaleditor"); - - template<> template<> - void alscriptexternaleditor_object::test<1>() + TEST_CASE_METHOD(alscriptexternaleditor_data, "alscriptexternaleditor: the copy is named for the tab and the bridge in the temp folder", "[alscriptexternaleditor]") { - set_test_name("the copy's name: the tab's without what a file system refuses, the bridge's id, the language's extension, in the temp folder"); ALScriptExternalEditor& unit = make(); Doc& a = tab("a", "x"); a.name = "myipt:?"; const std::string temp = LLFile::tmpdir(); - ensure_equals("LSL", unit.fileName(a), temp + "sl_script_myscript_" + studio.bridge + ".lsl"); + UNSCOPED_INFO("LSL"); + CHECK(unit.fileName(a) == temp + "sl_script_myscript_" + studio.bridge + ".lsl"); a.language.lua = true; a.name = "<>"; - ensure_equals("SLua, and a name of nothing but those", unit.fileName(a), temp + "sl_script_" + studio.bridge + ".luau"); + UNSCOPED_INFO("SLua, and a name of nothing but those"); + CHECK(unit.fileName(a) == temp + "sl_script_" + studio.bridge + ".luau"); } - template<> template<> - void alscriptexternaleditor_object::test<2>() + TEST_CASE_METHOD(alscriptexternaleditor_data, "alscriptexternaleditor: Edit Externally writes and watches a copy and starts the editor", "[alscriptexternaleditor]") { - set_test_name("Edit Externally writes the copy and watches it, tells the bridge and starts the editor; a file on disk as it is; a notecard not"); ALScriptExternalEditor& unit = make(); studio.bridgeThere = true; Doc& a = tab("a", "default {}"); unit.edit(a); const std::string file = unit.fileName(a); - ensure_equals("the copy", contents(file), std::string("default {}")); - ensure("watched", a.watch && a.watch->path() == file); - ensure("the log beside it", a.external->log && a.external->log->path() == file + ".log"); - ensure("the bridge told", studio.subscribed == Names{ "a" } && a.external->subscribed); - ensure("the editor started on it", studio.started == Names{ file }); - ensure_equals("what it holds, known", a.external->written, std::string("default {}")); + CHECK(contents(file) == std::string("default {}")); + REQUIRE(a.watch); + CHECK(a.watch->path() == file); + REQUIRE(a.external->log); + CHECK(a.external->log->path() == file + ".log"); + CHECK(studio.subscribed == Names{ "a" }); + CHECK(a.external->subscribed); + CHECK(studio.started == Names{ file }); + UNSCOPED_INFO("what it holds, known"); + CHECK(a.external->written == std::string("default {}")); Doc& e = tab("e", ""); unit.edit(e); - ensure_equals("an empty script is one space", contents(unit.fileName(e)), std::string(" ")); + UNSCOPED_INFO("an empty script is one space"); + CHECK(contents(unit.fileName(e)) == std::string(" ")); Doc& d = tab("d", "x"); d.file = "/somewhere/d.lsl"; unit.edit(d); - ensure("a file on disk watched where it is", - d.watch && d.watch->path() == "/somewhere/d.lsl" && studio.started.back() == "/somewhere/d.lsl on disk"); - ensure("with no log, and nothing for the bridge", !d.external->log && studio.subscribed.size() == 2); + { + INFO("a file on disk watched where it is"); + REQUIRE(d.watch); + CHECK(d.watch->path() == "/somewhere/d.lsl"); + CHECK(studio.started.back() == "/somewhere/d.lsl on disk"); + } + { + INFO("with no log, and nothing for the bridge"); + CHECK_FALSE(d.external->log); + CHECK(studio.subscribed.size() == 2); + } Doc& n = tab("n", "x"); n.notecard = true; unit.edit(n); - ensure("a notecard not", studio.started.size() == 3 && !n.watch); + { + INFO("a notecard not"); + CHECK(studio.started.size() == 3); + CHECK_FALSE(n.watch); + } } - template<> template<> - void alscriptexternaleditor_object::test<3>() + TEST_CASE_METHOD(alscriptexternaleditor_data, "alscriptexternaleditor: a save outside over an unchanged tab is taken and saved from here", "[alscriptexternaleditor]") { - set_test_name("a save outside over nothing changed here is taken, as one step, and saved from here"); ALScriptExternalEditor& unit = make(); Doc& a = tab("a", "one"); unit.edit(a); write(unit.fileName(a), "two"); unit.changed("a", unit.fileName(a)); - ensure("taken", studio.taken == Names{ "two" } && a.editor->text() == "two"); - ensure("saved from here, as the editor's", studio.saved == Names{ "a" } && a.save.external()); - ensure_equals("what the copy holds, known", a.external->written, std::string("two")); - ensure("nothing asked", !asked()); + CHECK(studio.taken == Names{ "two" }); + CHECK(a.editor->text() == "two"); + { + INFO("saved from here, as the editor's"); + CHECK(studio.saved == Names{ "a" }); + CHECK(a.save.external()); + } + UNSCOPED_INFO("what the copy holds, known"); + CHECK(a.external->written == std::string("two")); + CHECK_FALSE(asked()); a.editor->resetDirty(); unit.changed("a", unit.fileName(a)); - ensure("the same again, with the tab saved: nothing to take or save", studio.taken.size() == 1 && studio.saved.size() == 1); + { + INFO("the same again, with the tab saved: nothing to take or save"); + CHECK(studio.taken.size() == 1); + CHECK(studio.saved.size() == 1); + } unit.changed("nobody", unit.fileName(a)); - ensure("a tab gone: nothing", studio.taken.size() == 1); + UNSCOPED_INFO("a tab gone: nothing"); + CHECK(studio.taken.size() == 1); } - template<> template<> - void alscriptexternaleditor_object::test<4>() + TEST_CASE_METHOD(alscriptexternaleditor_data, "alscriptexternaleditor: a save outside over changes here is held until the author takes it", "[alscriptexternaleditor]") { - set_test_name("a save outside over changes made here is held and asked about; taken when the author says so"); ALScriptExternalEditor& unit = make(); Doc& a = tab("a", "one"); unit.edit(a); @@ -236,76 +254,93 @@ namespace tut a.editor->insertText(" here"); write(unit.fileName(a), "two"); unit.changed("a", unit.fileName(a)); - ensure("not taken", studio.taken.empty() && a.editor->text() == "one here"); - ensure("held", a.external->waiting && *a.external->waiting == "two"); - ensure("and asked about", asked() && services.reports.back().failure && services.reports.back().doc == "a"); + CHECK(studio.taken.empty()); + CHECK(a.editor->text() == "one here"); + REQUIRE(a.external->waiting); + CHECK(*a.external->waiting == "two"); + REQUIRE(asked()); + CHECK(services.reports.back().failure); + CHECK(services.reports.back().doc == "a"); unit.take(a, *a.external->waiting); - ensure("taken once said", studio.taken == Names{ "two" } && !a.external->waiting && studio.saved == Names{ "a" }); + { + INFO("taken once said"); + CHECK(studio.taken == Names{ "two" }); + CHECK_FALSE(a.external->waiting); + CHECK(studio.saved == Names{ "a" }); + } } - template<> template<> - void alscriptexternaleditor_object::test<5>() + TEST_CASE_METHOD(alscriptexternaleditor_data, "alscriptexternaleditor: a save with CRLF endings after one taken is taken without asking", "[alscriptexternaleditor]") { - set_test_name("a save with CRLF endings after one taken is taken, not asked about"); ALScriptExternalEditor& unit = make(); Doc& a = tab("a", "one"); unit.edit(a); write(unit.fileName(a), "a\r\nb"); unit.changed("a", unit.fileName(a)); - ensure("the first, its endings as the editor keeps them", studio.taken == Names{ "a\nb" }); + UNSCOPED_INFO("the first, its endings as the editor keeps them"); + CHECK(studio.taken == Names{ "a\nb" }); write(unit.fileName(a), "a\r\nb\r\nc"); unit.changed("a", unit.fileName(a)); - ensure("the second taken too", studio.taken == Names{ "a\nb", "a\nb\nc" } && !asked()); + CHECK(studio.taken == Names{ "a\nb", "a\nb\nc" }); + CHECK_FALSE(asked()); } - template<> template<> - void alscriptexternaleditor_object::test<6>() + TEST_CASE_METHOD(alscriptexternaleditor_data, "alscriptexternaleditor: an emptied copy is taken only if still empty a moment later", "[alscriptexternaleditor]") { - set_test_name("an emptied copy waits a moment; looked at again, taken if still empty, and left where the editor's second step came"); ALScriptExternalEditor& unit = make(); Doc& a = tab("a", "one"); unit.edit(a); write(unit.fileName(a), ""); unit.changed("a", unit.fileName(a)); - ensure("not taken at once", studio.taken.empty()); + UNSCOPED_INFO("not taken at once"); + CHECK(studio.taken.empty()); unit.changed("a", unit.fileName(a), true); - ensure("still empty a moment later: taken", studio.taken == Names{ "" }); + UNSCOPED_INFO("still empty a moment later: taken"); + CHECK(studio.taken == Names{ "" }); write(unit.fileName(a), "two"); unit.changed("a", unit.fileName(a)); unit.changed("a", unit.fileName(a), true); - ensure("the second step heard first: the look after takes nothing more", - studio.taken == Names{ "", "two" } && studio.saved.size() == 2); + { + INFO("the second step heard first: the look after takes nothing more"); + CHECK(studio.taken == Names{ "", "two" }); + CHECK(studio.saved.size() == 2); + } } - template<> template<> - void alscriptexternaleditor_object::test<7>() + TEST_CASE_METHOD(alscriptexternaleditor_data, "alscriptexternaleditor: a sync rewrites a changed copy and the compiler's words go in the log", "[alscriptexternaleditor]") { - set_test_name("after a save from here the copy is written again where it holds something else; the compiler's words go in the log"); ALScriptExternalEditor& unit = make(); Doc& a = tab("a", "one"); unit.edit(a); a.editor->setCaret(a.editor->document().end()); a.editor->insertText(" two"); unit.sync(a); - ensure_equals("written again", contents(unit.fileName(a)), std::string("one two")); - ensure_equals("and known", a.external->written, std::string("one two")); + CHECK(contents(unit.fileName(a)) == std::string("one two")); + UNSCOPED_INFO("what it holds, known"); + CHECK(a.external->written == std::string("one two")); ALScriptCompileResult result; result.messages = { "(1, 2) : ERROR : Syntax error\x07" }; unit.log(a, result); const std::string log = contents(a.external->log->path()); - ensure("the log: when, then the words, what cannot be printed dropped", - log.find("// [when]") == 0 && log.find("Syntax error\n") != std::string::npos && log.find('\x07') == std::string::npos); + { + INFO("the log: when, then the words, what cannot be printed dropped"); + CHECK(log.find("// [when]") == 0); + CHECK(log.find("Syntax error\n") != std::string::npos); + CHECK(log.find('\x07') == std::string::npos); + } Doc& d = tab("d", "x"); d.file = "/somewhere/d.lsl"; unit.log(d, result); unit.sync(d); - ensure("a tab not held outside: nothing", !d.external->log && !d.watch); + { + INFO("a tab not held outside: nothing"); + CHECK_FALSE(d.external->log); + CHECK_FALSE(d.watch); + } } - template<> template<> - void alscriptexternaleditor_object::test<8>() + TEST_CASE_METHOD(alscriptexternaleditor_data, "alscriptexternaleditor: letting go tells the bridge and removes the watch and copy and log", "[alscriptexternaleditor]") { - set_test_name("letting go tells the bridge, and the watch, the copy and the log go"); ALScriptExternalEditor& unit = make(); studio.bridgeThere = true; Doc& a = tab("a", "one"); @@ -314,19 +349,25 @@ namespace tut unit.log(a, result); const std::string file = unit.fileName(a); const std::string log = a.external->log->path(); - ensure("both there", exists(file) && exists(log)); + CHECK(exists(file)); + CHECK(exists(log)); a.save.fromExternal(1); unit.stop(a); - ensure("the bridge told", studio.unsubscribed == Names{ "a" } && !a.external->subscribed); - ensure("the watch gone, and the studio's copy with it", !a.watch && !exists(file)); - ensure("the log gone", !exists(log) && !a.external->log); - ensure("a save under way is no longer the editor's", !a.save.external()); + CHECK(studio.unsubscribed == Names{ "a" }); + CHECK_FALSE(a.external->subscribed); + { + INFO("the watch gone, and the studio's copy with it"); + CHECK_FALSE(a.watch); + CHECK_FALSE(exists(file)); + } + CHECK_FALSE(exists(log)); + CHECK_FALSE(a.external->log); + UNSCOPED_INFO("a save under way is no longer the editor's"); + CHECK_FALSE(a.save.external()); } - template<> template<> - void alscriptexternaleditor_object::test<9>() + TEST_CASE_METHOD(alscriptexternaleditor_data, "alscriptexternaleditor: keeping the tab's text over a held save lets it go and says so", "[alscriptexternaleditor]") { - set_test_name("a save outside held over changes made here, kept from when the author says so: let go, the tab's text standing, and said"); ALScriptExternalEditor& unit = make(); Doc& a = tab("a", "one"); unit.edit(a); @@ -334,9 +375,13 @@ namespace tut a.editor->insertText(" here"); write(unit.fileName(a), "two"); unit.changed("a", unit.fileName(a)); - ensure("held", a.external->waiting && *a.external->waiting == "two"); + REQUIRE(a.external->waiting); + CHECK(*a.external->waiting == "two"); unit.keep(a); - ensure("let go, the tab's text standing", !a.external->waiting && studio.taken.empty() && a.editor->text() == "one here"); - ensure("said", !services.statuses.empty() && services.statuses.back().find("ExternalKept") == 0); + CHECK_FALSE(a.external->waiting); + CHECK(studio.taken.empty()); + CHECK(a.editor->text() == "one here"); + REQUIRE_FALSE(services.statuses.empty()); + CHECK(services.statuses.back().find("ExternalKept") == 0); } } diff --git a/indra/newview/tests/alscriptinspectorpane_test.cpp b/indra/newview/tests/alscriptinspectorpane_test.cpp index cbee6c51f4..09adf68b36 100644 --- a/indra/newview/tests/alscriptinspectorpane_test.cpp +++ b/indra/newview/tests/alscriptinspectorpane_test.cpp @@ -33,7 +33,7 @@ #include "altextview.h" #include "llfocusmgr.h" -#include "../test/lltut.h" +#include #include @@ -70,7 +70,7 @@ namespace } } -namespace tut +namespace { struct alscriptinspectorpane_data { @@ -99,7 +99,7 @@ namespace tut ALScriptInspectorPane* found = window.find("inspector_pane"); if (!found) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } return found; } @@ -123,14 +123,8 @@ namespace tut std::string shown() { return pane()->view()->document().text(); } }; - typedef test_group alscriptinspectorpane_group; - typedef alscriptinspectorpane_group::object alscriptinspectorpane_object; - alscriptinspectorpane_group alscriptinspectorpane_instance("alscriptinspectorpane"); - - template<> template<> - void alscriptinspectorpane_object::test<1>() + TEST_CASE_METHOD(alscriptinspectorpane_data, "alscriptinspectorpane: a hover shows its declaration as a link and its type as code and page", "[alscriptinspectorpane]") { - set_test_name("a name's declaration, where it is declared as a link there, what is expected, its type as code, its words and page"); ALScriptInspectorPane* inspector = pane(); Doc& doc = tab("a"); ALScriptAnalysis::Result result = hover("integer count"); @@ -142,7 +136,7 @@ namespace tut result.hover.documentation = "How many."; result.hover.link = "https://example.com/count"; inspector->inspected(doc, result, ALTextPos(0, 8)); - ensure_equals("said", shown(), + CHECK(shown() == std::string("integer count\nDeclared on line 1.\nExpected here: float\n\ninteger\n count\n\nHow many.\n" "https://example.com/count")); const std::vector& links = inspector->view()->substitutions(); @@ -153,18 +147,41 @@ namespace tut to = link.range.begin == ALTextPos(1, 0) ? &link : to; url = link.range.begin == ALTextPos(8, 0) ? &link : url; } - ensure("the declaration's line a link there", to && to->link && to->tooltip == "Go to the declaration"); - ensure("to the line", to->value["line"].asInteger() == 0 && to->value["column"].asInteger() == 8); - ensure("in the script", to->value["path"].asString().empty()); - ensure("the page a link", url && url->link); + { + INFO("the declaration's line a link there"); + REQUIRE(to); + REQUIRE(to->link); + CHECK(to->tooltip == "Go to the declaration"); + } + { + INFO("to the line"); + CHECK(to->value["line"].asInteger() == 0); + CHECK(to->value["column"].asInteger() == 8); + } + UNSCOPED_INFO("in the script"); + REQUIRE(to->value["path"].asString().empty()); + { + INFO("the page a link"); + REQUIRE(url); + CHECK(url->link); + } std::set styled; for (const ALTextView::Style& style : inspector->view()->styles()) { styled.insert(style.range.begin.line); } - ensure("the declaration as code", styled.count(0) == 1); - ensure("the type as code", styled.count(4) == 1 && styled.count(5) == 1); - ensure("the words not", styled.count(7) == 0 && styled.count(2) == 0); + UNSCOPED_INFO("the declaration as code"); + REQUIRE(styled.count(0) == 1); + { + INFO("the type as code"); + REQUIRE(styled.count(4) == 1); + REQUIRE(styled.count(5) == 1); + } + { + INFO("the words not"); + REQUIRE(styled.count(7) == 0); + REQUIRE(styled.count(2) == 0); + } // Followed: the window's. window.floater->setVisible(true); @@ -174,57 +191,64 @@ namespace tut view->handleMouseDown(4, y, MASK_NONE); view->handleMouseUp(4, y, MASK_NONE); } - ensure("followed", told().went.size() == 1 && told().went[0]["line"].asInteger() == 0); + { + INFO("followed"); + REQUIRE(told().went.size() == 1); + CHECK(told().went[0]["line"].asInteger() == 0); + } } - template<> template<> - void alscriptinspectorpane_object::test<2>() + TEST_CASE_METHOD(alscriptinspectorpane_data, "alscriptinspectorpane: an answer for another tab or a place the caret has left is not shown", "[alscriptinspectorpane]") { - set_test_name("an answer for another tab, or a place the caret has left, not shown"); ALScriptInspectorPane* inspector = pane(); Doc& doc = tab("a"); Doc& other = tab("b"); inspector->show("before"); inspector->inspected(other, hover("x"), ALTextPos(0, 8)); - ensure_equals("another tab's", shown(), std::string("before")); + UNSCOPED_INFO("another tab's"); + CHECK(shown() == std::string("before")); inspector->inspected(doc, hover("x"), ALTextPos(1, 0)); - ensure_equals("the caret gone", shown(), std::string("before")); + UNSCOPED_INFO("the caret gone"); + CHECK(shown() == std::string("before")); inspector->inspected(doc, hover("x"), ALTextPos(0, 8)); - ensure_equals("this one", shown(), std::string("x")); + UNSCOPED_INFO("this one"); + CHECK(shown() == std::string("x")); inspector->forget(); - ensure_equals("forgotten", shown(), std::string()); + UNSCOPED_INFO("forgotten"); + CHECK(shown() == std::string()); } - template<> template<> - void alscriptinspectorpane_object::test<3>() + TEST_CASE_METHOD(alscriptinspectorpane_data, "alscriptinspectorpane: a built-in the analyzer has no words for shows the definitions' words", "[alscriptinspectorpane]") { - set_test_name("a word of the language's the analyzer has no words for: the definitions', and its page"); ALScriptInspectorPane* inspector = pane(); Doc& doc = tab("a", "default { state_entry() { llSay(0, \"hi\"); } }\n"); doc.editor->setCaret(ALTextPos(0, 28)); inspector->inspected(doc, hover("llSay(integer channel, string msg)"), ALTextPos(0, 8)); - ensure_equals("the definitions' words and page", shown(), + UNSCOPED_INFO("the definitions' words and page"); + CHECK(shown() == std::string("llSay(integer channel, string msg)\n\nSays it.\nhttps://wiki.example/llSay")); ALScriptAnalysis::Result own = hover("llSay(integer channel, string msg)"); own.hover.documentation = "The analyzer's."; inspector->inspected(doc, own, ALTextPos(0, 8)); - ensure_equals("the analyzer's first", shown(), std::string("llSay(integer channel, string msg)\n\nThe analyzer's.")); + UNSCOPED_INFO("the analyzer's first"); + CHECK(shown() == std::string("llSay(integer channel, string msg)\n\nThe analyzer's.")); own.hover.link = "https://mine.example"; inspector->inspected(doc, own, ALTextPos(0, 8)); - ensure("its own page", shown().find("\nhttps://mine.example") != std::string::npos); + UNSCOPED_INFO("its own page"); + REQUIRE(shown().find("\nhttps://mine.example") != std::string::npos); own.hover.documentation.clear(); inspector->inspected(doc, own, ALTextPos(0, 8)); - ensure_equals("the definitions' words, its own page", shown(), + UNSCOPED_INFO("the definitions' words, its own page"); + CHECK(shown() == std::string("llSay(integer channel, string msg)\n\nSays it.\nhttps://mine.example")); doc.editor->setCaret(ALTextPos(0, 0)); inspector->inspected(doc, hover("default"), ALTextPos(0, 8)); - ensure_equals("a word the definitions have not", shown(), std::string("default")); + UNSCOPED_INFO("a word the definitions have not"); + CHECK(shown() == std::string("default")); } - template<> template<> - void alscriptinspectorpane_object::test<4>() + TEST_CASE_METHOD(alscriptinspectorpane_data, "alscriptinspectorpane: a name declared in an include is said to be there and linked there", "[alscriptinspectorpane]") { - set_test_name("declared in an include, through the expansion: said in it, and linked there"); ALScriptInspectorPane* inspector = pane(); Doc& doc = tab("a"); told().expanded = true; @@ -242,35 +266,40 @@ namespace tut result.hover.definitionLine = 0; result.hover.definitionColumn = 8; inspector->inspected(doc, result, ALTextPos(0, 8)); - ensure_equals("in the include", shown(), std::string("integer count\nDeclared in inc.lsl on line 5.")); + UNSCOPED_INFO("in the include"); + CHECK(shown() == std::string("integer count\nDeclared in inc.lsl on line 5.")); const ALTextView::Substitution& to = inspector->view()->substitutions().back(); - ensure("linked there", to.value["path"].asString() == "disk:/inc.lsl" && to.value["line"].asInteger() == 4); + { + INFO("linked there"); + CHECK(to.value["path"].asString() == "disk:/inc.lsl"); + CHECK(to.value["line"].asInteger() == 4); + } result.hover.definitionLine = 3; inspector->inspected(doc, result, ALTextPos(0, 8)); - ensure_equals("from nothing of the source: not said", shown(), std::string("integer count")); + UNSCOPED_INFO("from nothing of the source: not said"); + CHECK(shown() == std::string("integer count")); } - template<> template<> - void alscriptinspectorpane_object::test<5>() + TEST_CASE_METHOD(alscriptinspectorpane_data, "alscriptinspectorpane: the squiggles under the caret are listed under the hover or alone", "[alscriptinspectorpane]") { - set_test_name("what is squiggled where the caret is, each with what it says, under the name or alone"); ALScriptInspectorPane* inspector = pane(); Doc& doc = tab("a"); doc.editor->setDecorations({ squiggle(0, 8, 13, "Unused."), squiggle(0, 0, 13, "Twice."), squiggle(0, 8, 13, ""), squiggle(0, 8, 13, "Lit.", ALCodeEditor::Decoration::Style::Background), squiggle(1, 0, 5, "Elsewhere.") }); - ensure_equals("each", inspector->problemsAt(doc, ALTextPos(0, 9)), std::string("Problem: Unused.\nProblem: Twice.")); - ensure_equals("none", inspector->problemsAt(doc, ALTextPos(2, 0)), std::string()); + UNSCOPED_INFO("each squiggle under the caret that says something"); + CHECK(inspector->problemsAt(doc, ALTextPos(0, 9)) == std::string("Problem: Unused.\nProblem: Twice.")); + CHECK(inspector->problemsAt(doc, ALTextPos(2, 0)) == std::string()); inspector->inspected(doc, hover("integer count"), ALTextPos(0, 8)); - ensure_equals("under the name", shown(), std::string("integer count\n\nProblem: Unused.\nProblem: Twice.")); + UNSCOPED_INFO("under the name"); + CHECK(shown() == std::string("integer count\n\nProblem: Unused.\nProblem: Twice.")); inspector->inspected(doc, ALScriptAnalysis::Result(), ALTextPos(0, 8)); - ensure_equals("alone", shown(), std::string("Problem: Unused.\nProblem: Twice.")); + UNSCOPED_INFO("alone"); + CHECK(shown() == std::string("Problem: Unused.\nProblem: Twice.")); } - template<> template<> - void alscriptinspectorpane_object::test<6>() + TEST_CASE_METHOD(alscriptinspectorpane_data, "alscriptinspectorpane: a name from a required module is said to be in its file and linked there", "[alscriptinspectorpane]") { - set_test_name("declared in a module the script requires, which the analyzers read apart: said in its file, and linked there"); ALScriptInspectorPane* inspector = pane(); Doc& doc = tab("a", "local util = require(\"util\")\nprint(util.twice(2))\n"); told().expanded = true; @@ -296,16 +325,23 @@ namespace tut result.hover.definitionColumn = 11; result.hover.definitionFile = "disk:/lib/util.luau"; inspector->inspected(doc, result, ALTextPos(0, 8)); - ensure_equals("in the module", shown(), std::string("function M.twice(n: number): number\nDeclared in util.luau on line 4.")); + UNSCOPED_INFO("in the module"); + CHECK(shown() == std::string("function M.twice(n: number): number\nDeclared in util.luau on line 4.")); const ALTextView::Substitution& to = inspector->view()->substitutions().back(); - ensure("linked there", to.value["path"].asString() == "disk:/lib/util.luau" && to.value["line"].asInteger() == 3 && - to.value["column"].asInteger() == 11); + { + INFO("linked there"); + CHECK(to.value["path"].asString() == "disk:/lib/util.luau"); + CHECK(to.value["line"].asInteger() == 3); + CHECK(to.value["column"].asInteger() == 11); + } result.hover.definitionFile = "disk:/lib/gone.luau"; inspector->inspected(doc, result, ALTextPos(0, 8)); - ensure_equals("a module the expansion has not: not said", shown(), std::string("function M.twice(n: number): number")); + UNSCOPED_INFO("a module the expansion has not: not said"); + CHECK(shown() == std::string("function M.twice(n: number): number")); result.hover.definitionFile = "disk:/lib/util.luau"; told().expanded = false; inspector->inspected(doc, result, ALTextPos(0, 8)); - ensure_equals("nor where the script is not expanded", shown(), std::string("function M.twice(n: number): number")); + UNSCOPED_INFO("nor where the script is not expanded"); + CHECK(shown() == std::string("function M.twice(n: number): number")); } } diff --git a/indra/newview/tests/alscriptitemdrop_test.cpp b/indra/newview/tests/alscriptitemdrop_test.cpp index 605164b940..bee2fd75d2 100644 --- a/indra/newview/tests/alscriptitemdrop_test.cpp +++ b/indra/newview/tests/alscriptitemdrop_test.cpp @@ -34,11 +34,11 @@ #include "alscriptstudio_fixture.h" -#include "../test/lltut.h" +#include #include -namespace tut +namespace { struct alscriptitemdrop_data { @@ -53,7 +53,7 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } LLUUID object, item; object.generate(); @@ -103,47 +103,58 @@ namespace tut return accept; } }; - typedef test_group alscriptitemdrop_group; - typedef alscriptitemdrop_group::object alscriptitemdrop_object; - alscriptitemdrop_group alscriptitemdrop_instance("ALScriptItemDrop"); - template<> template<> - void alscriptitemdrop_object::test<1>() + TEST_CASE_METHOD(alscriptitemdrop_data, "ALScriptItemDrop: a dropped item's name goes in escaped and with Shift its key if seen", "[ALScriptItemDrop]") { - set_test_name("its name goes in where it lands, as a string, its quotes escaped; with Shift its key, where it may be seen"); - ensure_equals("quoted and escaped", ALScriptItemDrop::literal("a \"b\" \\ c"), std::string("\"a \\\"b\\\" \\\\ c\"")); + UNSCOPED_INFO("quoted and escaped"); + CHECK(ALScriptItemDrop::literal("a \"b\" \\ c") == std::string("\"a \\\"b\\\" \\\\ c\"")); make("x;"); const auto wood = item("Wood \"oak\""); std::string tip; - ensure("offered", drop(wood, false, MASK_NONE, &tip) == ACCEPT_YES_SINGLE); - ensure("the tip says what goes in", tip.find("\"Wood \\\"oak\\\"\"") != std::string::npos); - ensure("nothing yet", doc->editor->text() == "x;"); + UNSCOPED_INFO("offered"); + CHECK(drop(wood, false, MASK_NONE, &tip) == ACCEPT_YES_SINGLE); + UNSCOPED_INFO("the tip says what goes in"); + CHECK(tip.find("\"Wood \\\"oak\\\"\"") != std::string::npos); + UNSCOPED_INFO("a drag over changes nothing"); + CHECK(doc->editor->text() == "x;"); drop(wood, true); - ensure_equals("in where it lands", doc->editor->text(), std::string("\"Wood \\\"oak\\\"\"x;")); - ensure("the caret after it", doc->editor->caret() == ALTextPos(0, 14)); + UNSCOPED_INFO("in where it lands"); + CHECK(doc->editor->text() == std::string("\"Wood \\\"oak\\\"\"x;")); + UNSCOPED_INFO("the caret after it"); + CHECK(doc->editor->caret() == ALTextPos(0, 14)); doc->editor->setText("x;"); - ensure("its key not to be seen: refused", drop(wood, false, MASK_SHIFT, &tip) == ACCEPT_NO && !tip.empty()); + { + INFO("its key not to be seen: refused"); + CHECK(drop(wood, false, MASK_SHIFT, &tip) == ACCEPT_NO); + CHECK_FALSE(tip.empty()); + } shown.generate(); drop(wood, true, MASK_SHIFT); - ensure_equals("its key", doc->editor->text(), "\"" + shown.asString() + "\"x;"); + UNSCOPED_INFO("its key"); + CHECK(doc->editor->text() == "\"" + shown.asString() + "\"x;"); } - template<> template<> - void alscriptitemdrop_object::test<2>() + TEST_CASE_METHOD(alscriptitemdrop_data, "ALScriptItemDrop: a read-only script refuses a drop and folders and notecards go elsewhere", "[ALScriptItemDrop]") { - set_test_name("a script that may not be changed refuses it; a folder, and a notecard, are left to someone else"); make("x;"); const auto wood = item("Wood"); doc->modifiable = false; std::string tip; - ensure("refused, and said why", drop(wood, true, MASK_NONE, &tip) == ACCEPT_NO && !tip.empty() && doc->editor->text() == "x;"); + { + INFO("refused, and said why"); + CHECK(drop(wood, true, MASK_NONE, &tip) == ACCEPT_NO); + CHECK_FALSE(tip.empty()); + CHECK(doc->editor->text() == "x;"); + } doc->modifiable = true; bool taken = true; drop(wood, false, MASK_NONE, nullptr, DAD_CATEGORY, &taken); - ensure("a folder: not taken", !taken); + UNSCOPED_INFO("a folder: not taken"); + CHECK_FALSE(taken); doc->notecard = true; drop(wood, false, MASK_NONE, nullptr, DAD_TEXTURE, &taken); - ensure("a notecard: not taken", !taken); + UNSCOPED_INFO("a notecard: not taken"); + CHECK_FALSE(taken); } } diff --git a/indra/newview/tests/alscriptjobqueue_test.cpp b/indra/newview/tests/alscriptjobqueue_test.cpp index b604706826..7126db4174 100644 --- a/indra/newview/tests/alscriptjobqueue_test.cpp +++ b/indra/newview/tests/alscriptjobqueue_test.cpp @@ -26,9 +26,9 @@ #include "../alscriptjobqueue.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { struct alscriptjobqueue_data { @@ -47,130 +47,164 @@ namespace tut } }; - typedef test_group alscriptjobqueue_group; - typedef alscriptjobqueue_group::object alscriptjobqueue_object; - alscriptjobqueue_group alscriptjobqueue_instance("alscriptjobqueue"); - - template<> template<> - void alscriptjobqueue_object::test<1>() + TEST_CASE_METHOD(alscriptjobqueue_data, "alscriptjobqueue: the latest job of each key waits and they run by rank then by age", "[alscriptjobqueue]") { - set_test_name("only the latest of each kind about each script waits, and the lowest rank goes first, the oldest first among equals"); queue.add("back/check", "back", 1, 3, "back-check-1"); queue.add("front/check", "front", 1, 1, "front-check-1"); queue.add("front/check", "front", 2, 1, "front-check-2"); queue.add("front/hover", "front", 2, 0, "front-hover"); queue.add("front/weigh", "front", 2, 2, "front-weigh"); queue.add("other/check", "other", 4, 3, "other-check"); - ensure_equals("one of each key", queue.waiting(), size_t(5)); - ensure_equals("by rank, then by when", drained(), std::string("front-hover front-check-2 front-weigh back-check-1 other-check")); - ensure_equals("nothing passed over", queue.passedOver(), size_t(0)); + UNSCOPED_INFO("one of each key"); + CHECK(queue.waiting() == size_t(5)); + UNSCOPED_INFO("by rank, then by when"); + CHECK(drained() == std::string("front-hover front-check-2 front-weigh back-check-1 other-check")); + CHECK(queue.passedOver() == size_t(0)); } - template<> template<> - void alscriptjobqueue_object::test<2>() + TEST_CASE_METHOD(alscriptjobqueue_data, "alscriptjobqueue: a question about a stale text is passed over but not across a reopen", "[alscriptjobqueue]") { - set_test_name("a question about a text older than one asked about since is passed over; one asked before a newer text of a script opened again is not"); queue.add("a/hover", "a", 5, 0, "hover-5"); queue.add("a/check", "a", 6, 1, "check-6"); - ensure_equals("the hover of the older text dropped as it is reached", drained(), std::string("check-6")); - ensure_equals("and counted", queue.passedOver(), size_t(1)); + UNSCOPED_INFO("the hover of the older text dropped as it is reached"); + CHECK(drained() == std::string("check-6")); + UNSCOPED_INFO("and counted"); + CHECK(queue.passedOver() == size_t(1)); // A script closed and opened again starts its versions over: what // its old text left waiting does not make the new one's stale. queue.add("b/hover", "b", 40, 0, "old-hover-40"); queue.add("b/check", "b", 1, 1, "new-check-1"); - ensure_equals("both run", drained(), std::string("old-hover-40 new-check-1")); + UNSCOPED_INFO("both run"); + CHECK(drained() == std::string("old-hover-40 new-check-1")); queue.add("c/check", "c", 1, 1, "c-check"); queue.add("d/check", "d", 1, 1, "d-check"); queue.forget("c"); - ensure_equals("a script let go of: nothing it asked runs", drained(), std::string("d-check")); + UNSCOPED_INFO("a script let go of: nothing it asked runs"); + CHECK(drained() == std::string("d-check")); } - template<> template<> - void alscriptjobqueue_object::test<3>() + TEST_CASE_METHOD(alscriptjobqueue_data, "alscriptjobqueue: the running job is superseded only by its own key or a newer text", "[alscriptjobqueue]") { - set_test_name("the running job is told its answer is unwanted by its own key asked again or a newer text of its script, and by nothing else"); queue.add("a/check", "a", 3, 1, "check-3"); - ensure("taken", queue.take().has_value()); - ensure("nothing asked since", !queue.superseded()); - ensure("another script's question leaves it be", !queue.add("b/check", "b", 9, 1, "b-check")); - ensure("another kind, the same text, leaves it be", !queue.add("a/hover", "a", 3, 0, "hover-3")); - ensure("an older text's leaves it be", !queue.add("a/signature", "a", 2, 0, "signature-2")); - ensure("not yet", !queue.superseded()); - ensure("its own key asked again stops it", queue.add("a/check", "a", 3, 1, "check-3-again")); - ensure("unwanted", queue.superseded()); - ensure("said once", !queue.add("a/check", "a", 4, 1, "check-4")); + REQUIRE(queue.take().has_value()); + UNSCOPED_INFO("nothing asked since"); + CHECK_FALSE(queue.superseded()); + UNSCOPED_INFO("another script's question leaves it be"); + CHECK_FALSE(queue.add("b/check", "b", 9, 1, "b-check")); + UNSCOPED_INFO("another kind, the same text, leaves it be"); + CHECK_FALSE(queue.add("a/hover", "a", 3, 0, "hover-3")); + UNSCOPED_INFO("an older text's leaves it be"); + CHECK_FALSE(queue.add("a/signature", "a", 2, 0, "signature-2")); + UNSCOPED_INFO("not yet"); + CHECK_FALSE(queue.superseded()); + UNSCOPED_INFO("its own key asked again stops it"); + CHECK(queue.add("a/check", "a", 3, 1, "check-3-again")); + CHECK(queue.superseded()); + UNSCOPED_INFO("said once"); + CHECK_FALSE(queue.add("a/check", "a", 4, 1, "check-4")); queue.finished(); - ensure("done with", !queue.superseded()); + UNSCOPED_INFO("done with"); + CHECK_FALSE(queue.superseded()); queue.add("x/hover", "x", 1, 0, "x-hover"); - ensure("taken", queue.take().has_value()); - ensure("a newer text of its script stops it", queue.add("x/check", "x", 2, 1, "x-check")); + REQUIRE(queue.take().has_value()); + UNSCOPED_INFO("a newer text of its script stops it"); + CHECK(queue.add("x/check", "x", 2, 1, "x-check")); queue.finished(); } - template<> template<> - void alscriptjobqueue_object::test<4>() + TEST_CASE_METHOD(alscriptjobqueue_data, "alscriptjobqueue: a yielding job stops for a rank 0 question and runs after it", "[alscriptjobqueue]") { - set_test_name("a job that yields is stopped by a question of the lowest rank, waits again behind it and runs after; one that does not yield runs on"); queue.add("back/check", "back", 1, 4, "back-check", true); - ensure("taken", queue.take().has_value()); - ensure("a check of the front tab leaves it be", !queue.add("front/check", "front", 1, 1, "front-check")); - ensure("a question of the front tab stops it", queue.add("front/hover", "front", 1, 0, "front-hover")); - ensure("as it yields", queue.yielded()); - ensure("its answer wanted still", !queue.superseded()); - ensure("said once", !queue.add("front/signature", "front", 1, 0, "front-signature")); + REQUIRE(queue.take().has_value()); + UNSCOPED_INFO("a check of the front tab leaves it be"); + CHECK_FALSE(queue.add("front/check", "front", 1, 1, "front-check")); + UNSCOPED_INFO("a question of the front tab stops it"); + CHECK(queue.add("front/hover", "front", 1, 0, "front-hover")); + CHECK(queue.yielded()); + UNSCOPED_INFO("its answer wanted still"); + CHECK_FALSE(queue.superseded()); + UNSCOPED_INFO("said once"); + CHECK_FALSE(queue.add("front/signature", "front", 1, 0, "front-signature")); queue.requeue("back-check"); queue.finished(); - ensure_equals("it runs after what it yielded to", drained(), std::string("front-hover front-signature front-check back-check")); + UNSCOPED_INFO("it runs after what it yielded to"); + CHECK(drained() == std::string("front-hover front-signature front-check back-check")); // Asked again while it ran: the newer stands for it. queue.add("back/check", "back", 1, 4, "back-check-1", true); - ensure("taken", queue.take().has_value()); - ensure("stopped", queue.add("front/hover", "front", 2, 0, "front-hover-2")); + REQUIRE(queue.take().has_value()); + UNSCOPED_INFO("stopped"); + CHECK(queue.add("front/hover", "front", 2, 0, "front-hover-2")); queue.add("back/check", "back", 2, 4, "back-check-2", true); queue.requeue("back-check-1"); queue.finished(); - ensure_equals("the newer alone", drained(), std::string("front-hover-2 back-check-2")); + UNSCOPED_INFO("the newer alone"); + CHECK(drained() == std::string("front-hover-2 back-check-2")); // One that does not yield runs on. queue.add("back/weigh", "back", 3, 3, "back-weigh"); - ensure("taken", queue.take().has_value()); - ensure("left be", !queue.add("front/hover", "front", 3, 0, "front-hover-3") && !queue.yielded()); + REQUIRE(queue.take().has_value()); + { + INFO("left be"); + CHECK_FALSE(queue.add("front/hover", "front", 3, 0, "front-hover-3")); + CHECK_FALSE(queue.yielded()); + } queue.finished(); drained(); } - template<> template<> - void alscriptjobqueue_object::test<5>() + TEST_CASE_METHOD(alscriptjobqueue_data, "alscriptjobqueue: a yielded job keeps its turn and one forgotten while running is dropped", "[alscriptjobqueue]") { - set_test_name("a job that yielded waits again in the turn it was asked at, and runs through the next time; one let go of as it runs is unwanted and waits no more"); queue.add("back/check", "back", 1, 4, "back-check", true); - ensure("taken", queue.take().has_value()); + REQUIRE(queue.take().has_value()); queue.add("other/check", "other", 1, 4, "other-check", true); - ensure("stopped by a question of the front tab", queue.add("front/hover", "front", 1, 0, "front-hover") && queue.yielded()); + { + INFO("stopped by a question of the front tab"); + CHECK(queue.add("front/hover", "front", 1, 0, "front-hover")); + CHECK(queue.yielded()); + } queue.requeue("back-check"); queue.finished(); auto next = queue.take(); - ensure("the question first", next && next->second == "front-hover"); + { + INFO("the question first"); + REQUIRE(next); + CHECK(next->second == "front-hover"); + } queue.finished(); next = queue.take(); - ensure("then it, ahead of what was asked after it", next && next->second == "back-check"); - ensure("not stopped again", !queue.add("front/hover", "front", 1, 0, "front-hover-again") && !queue.yielded()); - ensure("its answer wanted", !queue.superseded()); + { + INFO("then it, ahead of what was asked after it"); + REQUIRE(next); + CHECK(next->second == "back-check"); + } + { + INFO("not stopped again"); + CHECK_FALSE(queue.add("front/hover", "front", 1, 0, "front-hover-again")); + CHECK_FALSE(queue.yielded()); + } + UNSCOPED_INFO("its answer wanted"); + CHECK_FALSE(queue.superseded()); queue.finished(); - ensure_equals("the rest after", drained(), std::string("front-hover-again other-check")); + UNSCOPED_INFO("the rest after"); + CHECK(drained() == std::string("front-hover-again other-check")); // Let go of while it runs: unwanted, said once, and not put back // though it was stopped as it yielded. queue.add("gone/check", "gone", 1, 4, "gone-check", true); - ensure("taken", queue.take().has_value()); + REQUIRE(queue.take().has_value()); queue.add("gone/hover", "gone", 1, 0, "gone-hover"); - ensure("yielded", queue.yielded()); - ensure("let go of: to be stopped", queue.forget("gone")); - ensure("unwanted", queue.superseded()); - ensure("said once", !queue.forget("gone")); + CHECK(queue.yielded()); + UNSCOPED_INFO("let go of: to be stopped"); + CHECK(queue.forget("gone")); + CHECK(queue.superseded()); + UNSCOPED_INFO("said once"); + CHECK_FALSE(queue.forget("gone")); queue.requeue("gone-check"); queue.finished(); - ensure_equals("nothing of it waits", queue.waiting(), size_t(0)); - ensure("another script's running job left be", !queue.forget("other")); + UNSCOPED_INFO("nothing of it waits"); + CHECK(queue.waiting() == size_t(0)); + UNSCOPED_INFO("another script's running job left be"); + CHECK_FALSE(queue.forget("other")); } } diff --git a/indra/newview/tests/alscriptkeymap_test.cpp b/indra/newview/tests/alscriptkeymap_test.cpp index 4a75dec821..9979bdd2f1 100644 --- a/indra/newview/tests/alscriptkeymap_test.cpp +++ b/indra/newview/tests/alscriptkeymap_test.cpp @@ -33,7 +33,7 @@ #include "llcontrol.h" #include "llkeyboard.h" -#include "../test/lltut.h" +#include #include #include @@ -50,7 +50,7 @@ namespace }; } -namespace tut +namespace { struct alscriptkeymap_data { @@ -92,107 +92,150 @@ namespace tut return std::find(keys.begin(), keys.end(), chord) != keys.end(); } }; - typedef test_group alscriptkeymap_group; - typedef alscriptkeymap_group::object alscriptkeymap_object; - alscriptkeymap_group alscriptkeymap_instance("alscriptkeymap"); - template<> template<> - void alscriptkeymap_object::test<1>() + TEST_CASE_METHOD(alscriptkeymap_data, "alscriptkeymap: a person's editor keys replace the standard's and follow the setting", "[alscriptkeymap]") { - set_test_name("the editors' keymap: the standard's, a person's keys over it, built again only when the setting changes"); const ALKeymap* first = &ALScriptKeymap::current(); - ensure("the standard's", first->lookup('F', MASK_CONTROL) == C::Find); - ensure("the same keymap asked again", &ALScriptKeymap::current() == first); + UNSCOPED_INFO("the standard's"); + CHECK(first->lookup('F', MASK_CONTROL) == C::Find); + UNSCOPED_INFO("the same keymap asked again"); + CHECK(&ALScriptKeymap::current() == first); ALScriptKeymap::rebind(C::Find, { { 'Y', MASK_CONTROL | MASK_SHIFT } }); - ensure("the new key", ALScriptKeymap::current().lookup('Y', MASK_CONTROL | MASK_SHIFT) == C::Find); - ensure("the old one gone", ALScriptKeymap::current().lookup('F', MASK_CONTROL) == C::None); - ensure("said rebound", ALScriptKeymap::isRebound(C::Find) && !ALScriptKeymap::isRebound(C::Replace)); + UNSCOPED_INFO("the new key"); + CHECK(ALScriptKeymap::current().lookup('Y', MASK_CONTROL | MASK_SHIFT) == C::Find); + UNSCOPED_INFO("the old one gone"); + CHECK(ALScriptKeymap::current().lookup('F', MASK_CONTROL) == C::None); + { + INFO("said rebound"); + CHECK(ALScriptKeymap::isRebound(C::Find)); + CHECK_FALSE(ALScriptKeymap::isRebound(C::Replace)); + } // Set by anything else -- the preferences' Cancel putting it back. gSavedSettings.setLLSD("ALScriptStudioKeymap", LLSD::emptyMap()); - ensure("the standard's again", ALScriptKeymap::current().lookup('F', MASK_CONTROL) == C::Find); + UNSCOPED_INFO("the standard's again"); + CHECK(ALScriptKeymap::current().lookup('F', MASK_CONTROL) == C::Find); ALScriptKeymap::rebind(C::Find, {}); - ensure("none: unbound", ALScriptKeymap::keysOf(ALScriptKeymap::current(), C::Find).empty()); + UNSCOPED_INFO("none: unbound"); + REQUIRE(ALScriptKeymap::keysOf(ALScriptKeymap::current(), C::Find).empty()); ALScriptKeymap::restore(C::Find); - ensure("restored", ALScriptKeymap::current().lookup('F', MASK_CONTROL) == C::Find && !ALScriptKeymap::isRebound(C::Find)); + { + INFO("restored"); + CHECK(ALScriptKeymap::current().lookup('F', MASK_CONTROL) == C::Find); + CHECK_FALSE(ALScriptKeymap::isRebound(C::Find)); + } } - template<> template<> - void alscriptkeymap_object::test<2>() + TEST_CASE_METHOD(alscriptkeymap_data, "alscriptkeymap: menu keys may be several or two in turn and restore to the standard's", "[alscriptkeymap]") { - set_test_name("the menus' keys: several as standard, two in turn, a person's in their place, and the standard's again"); #if LL_DARWIN constexpr MASK REAL_CONTROL = MASK_MAC_CONTROL; #else constexpr MASK REAL_CONTROL = MASK_CONTROL; #endif const ALScriptKeymap::chords_t next = ALScriptKeymap::menuKeys("next_tab"); - ensure("next tab: Control-PgDn, which the menu shows", !next.empty() && next.front() == ALKeyChord{ KEY_PAGE_DOWN, MASK_CONTROL }); - ensure("and Control-Tab", has(next, ALKeyChord{ KEY_TAB, REAL_CONTROL })); - ensure("save all: Control-K, then S", ALScriptKeymap::menuKey("save_all") == (ALKeyChord{ 'S', MASK_NONE, 'K', MASK_CONTROL })); - ensure("a command with none", ALScriptKeymap::menuKeys("revert").empty() && ALScriptKeymap::isMenuCommand("revert")); - ensure("no such command", !ALScriptKeymap::isMenuCommand("frobnicate") && ALScriptKeymap::menuKeys("frobnicate").empty()); + { + INFO("next tab: Control-PgDn, which the menu shows"); + REQUIRE_FALSE(next.empty()); + CHECK(next.front() == ALKeyChord{ KEY_PAGE_DOWN, MASK_CONTROL }); + } + UNSCOPED_INFO("and Control-Tab"); + CHECK(has(next, ALKeyChord{ KEY_TAB, REAL_CONTROL })); + UNSCOPED_INFO("save all: Control-K, then S"); + CHECK(ALScriptKeymap::menuKey("save_all") == (ALKeyChord{ 'S', MASK_NONE, 'K', MASK_CONTROL })); + { + INFO("a command with none"); + REQUIRE(ALScriptKeymap::menuKeys("revert").empty()); + CHECK(ALScriptKeymap::isMenuCommand("revert")); + } + { + INFO("no such command"); + CHECK_FALSE(ALScriptKeymap::isMenuCommand("frobnicate")); + REQUIRE(ALScriptKeymap::menuKeys("frobnicate").empty()); + } const ALKeyChord two{ 'T', MASK_NONE, 'K', MASK_CONTROL }; ALScriptKeymap::rebindMenu("revert", { two, ALKeyChord{ KEY_F9, MASK_SHIFT } }); const ALScriptKeymap::chords_t mine = ALScriptKeymap::menuKeys("revert"); - ensure("kept, two in turn among them", mine.size() == 2 && mine[0] == two && mine[1] == (ALKeyChord{ KEY_F9, MASK_SHIFT })); - ensure("said rebound", ALScriptKeymap::isMenuRebound("revert")); + { + INFO("kept, two in turn among them"); + REQUIRE(mine.size() == 2); + CHECK(mine[0] == two); + CHECK(mine[1] == (ALKeyChord{ KEY_F9, MASK_SHIFT })); + } + UNSCOPED_INFO("said rebound"); + CHECK(ALScriptKeymap::isMenuRebound("revert")); ALScriptKeymap::rebindMenu("next_tab", {}); - ensure("none at all, in place of the standard's", ALScriptKeymap::menuKeys("next_tab").empty() && ALScriptKeymap::menuKey("next_tab").none()); + { + INFO("none at all, in place of the standard's"); + REQUIRE(ALScriptKeymap::menuKeys("next_tab").empty()); + CHECK(ALScriptKeymap::menuKey("next_tab").none()); + } ALScriptKeymap::restoreMenu("next_tab"); - ensure("the standard's again", ALScriptKeymap::menuKeys("next_tab") == next && !ALScriptKeymap::isMenuRebound("next_tab")); + { + INFO("the standard's again"); + CHECK(ALScriptKeymap::menuKeys("next_tab") == next); + CHECK_FALSE(ALScriptKeymap::isMenuRebound("next_tab")); + } ALScriptKeymap::restoreAll(); - ensure("all of them", ALScriptKeymap::menuKeys("revert").empty()); + UNSCOPED_INFO("all of them"); + REQUIRE(ALScriptKeymap::menuKeys("revert").empty()); } - template<> template<> - void alscriptkeymap_object::test<3>() + TEST_CASE_METHOD(alscriptkeymap_data, "alscriptkeymap: the menus' table has unique ids and keys and no Control-Alt typing key", "[alscriptkeymap]") { - set_test_name("the menus' table: each command once in its ids, each key the standard's once, and no Control-Alt with a key that types"); const std::vector& ids = ALScriptKeymap::menuIds(); - ensure_equals("each once", std::set(ids.begin(), ids.end()).size(), ids.size()); + UNSCOPED_INFO("each once"); + REQUIRE(std::set(ids.begin(), ids.end()).size() == ids.size()); std::set> seen; for (const std::string& id : ids) { for (const ALKeyChord& chord : ALScriptKeymap::menuKeys(id)) { - ensure("one command's key: " + id + " " + chord.describe(), seen.insert({ chord.key, chord.mask, chord.leadKey, chord.leadMask }).second); + UNSCOPED_INFO("one command's key: " << id << " " << chord.describe()); + REQUIRE(seen.insert({ chord.key, chord.mask, chord.leadKey, chord.leadMask }).second); for (const auto& [key, mask] : { std::pair{ chord.key, chord.mask }, std::pair{ chord.leadKey, chord.leadMask } }) { const bool types = key >= 0x20 && key < 0x80; - ensure("no Control-Alt: " + id, !types || (mask & (MASK_CONTROL | MASK_ALT)) != (MASK_CONTROL | MASK_ALT)); + UNSCOPED_INFO("no Control-Alt: " << id); + REQUIRE((!types || (mask & (MASK_CONTROL | MASK_ALT)) != (MASK_CONTROL | MASK_ALT))); } } } } - template<> template<> - void alscriptkeymap_object::test<4>() + TEST_CASE_METHOD(alscriptkeymap_data, "alscriptkeymap: every studio menu bar item can take keys and has a unique id and a path", "[alscriptkeymap]") { - set_test_name("every item of the studio's menu bar can take keys: the editors' command, or one of the table's; named by where it is, and the only one so named"); LLXMLNodePtr root; - ensure("the studio's file", LLXMLNode::parseFile(std::string(LLUI_TEST_APP_DIR) + "/skins/default/xui/en/floater_script_studio.xml", root)); + UNSCOPED_INFO("the studio's file"); + REQUIRE(LLXMLNode::parseFile(std::string(LLUI_TEST_APP_DIR) + "/skins/default/xui/en/floater_script_studio.xml", root)); const std::vector items = ALScriptKeymap::menuItemsIn(root); - ensure("its menus", items.size() > 50); + UNSCOPED_INFO("its menus"); + REQUIRE(items.size() > 50); std::set ids; for (const ALScriptKeymap::MenuItem& item : items) { - ensure("the only item so named: " + item.id, ids.insert(item.id).second); - ensure("can take keys: " + item.id, alEditorCommandFromName(item.id).has_value() || ALScriptKeymap::isMenuCommand(item.id)); - ensure("named by its menus: " + item.id, item.path.find(" > ") != std::string::npos); + UNSCOPED_INFO("the only item so named: " << item.id); + REQUIRE(ids.insert(item.id).second); + UNSCOPED_INFO("can take keys: " << item.id); + REQUIRE((alEditorCommandFromName(item.id).has_value() || ALScriptKeymap::isMenuCommand(item.id))); + UNSCOPED_INFO("named by its menus: " << item.id); + REQUIRE(item.path.find(" > ") != std::string::npos); } for (const std::string& id : ALScriptKeymap::menuIds()) { - ensure("the table's commands are the menus': " + id, ids.count(id) == 1); + UNSCOPED_INFO("the table's commands are the menus': " << id); + REQUIRE(ids.count(id) == 1); } const auto map = std::find_if(items.begin(), items.end(), [](const ALScriptKeymap::MenuItem& item) { return item.id == "map_left"; }); - ensure("as deep as it goes", map != items.end() && map->path == "View > Editor > Scrollbar > Map on the Left"); + { + INFO("as deep as it goes"); + REQUIRE(map != items.end()); + CHECK(map->path == "View > Editor > Scrollbar > Map on the Left"); + } } - template<> template<> - void alscriptkeymap_object::test<5>() + TEST_CASE_METHOD(alscriptkeymap_data, "alscriptkeymap: takeKeys takes a chord from whichever commands held it", "[alscriptkeymap]") { - set_test_name("keys given take them from whatever had them: an editor's command by the first key, the menus' by the keys, their first, or the given's first"); ALScriptKeymap::Owner revert; revert.menu = "revert"; const auto named = [](const std::vector& owners, const std::string& menu, C command = C::None) { @@ -201,20 +244,40 @@ namespace tut }; std::vector from = ALScriptKeymap::takeKeys(revert, ALKeyChord{ 'F', MASK_CONTROL }, false); - ensure("Find's, the editor's", from.size() == 1 && named(from, "", C::Find)); - ensure("named, not taken", ALScriptKeymap::current().lookup('F', MASK_CONTROL) == C::Find); + { + INFO("Find's, the editor's"); + REQUIRE(from.size() == 1); + CHECK(named(from, "", C::Find)); + } + UNSCOPED_INFO("named, not taken"); + CHECK(ALScriptKeymap::current().lookup('F', MASK_CONTROL) == C::Find); ALScriptKeymap::takeKeys(revert, ALKeyChord{ 'F', MASK_CONTROL }, true); - ensure("taken", ALScriptKeymap::current().lookup('F', MASK_CONTROL) == C::None); + UNSCOPED_INFO("taken"); + CHECK(ALScriptKeymap::current().lookup('F', MASK_CONTROL) == C::None); ALScriptKeymap::restoreAll(); from = ALScriptKeymap::takeKeys(revert, ALKeyChord{ 'K', MASK_CONTROL }, true); - ensure("every two keys it was the first of", named(from, "save_all") && named(from, "explorer") && named(from, "expanded")); - ensure("which have none now", ALScriptKeymap::menuKeys("save_all").empty() && ALScriptKeymap::menuKeys("explorer").empty()); + { + INFO("every two keys it was the first of"); + CHECK(named(from, "save_all")); + CHECK(named(from, "explorer")); + CHECK(named(from, "expanded")); + } + { + INFO("which have none now"); + REQUIRE(ALScriptKeymap::menuKeys("save_all").empty()); + REQUIRE(ALScriptKeymap::menuKeys("explorer").empty()); + } ALScriptKeymap::restoreAll(); from = ALScriptKeymap::takeKeys(revert, ALKeyChord{ 'X', MASK_NONE, 'P', MASK_CONTROL }, true); - ensure("the key that is the first of the two given", from.size() == 1 && named(from, "quick_open")); - ensure("which it had alone", ALScriptKeymap::menuKeys("quick_open").empty()); + { + INFO("the key that is the first of the two given"); + REQUIRE(from.size() == 1); + CHECK(named(from, "quick_open")); + } + UNSCOPED_INFO("which it had alone"); + REQUIRE(ALScriptKeymap::menuKeys("quick_open").empty()); ALScriptKeymap::restoreAll(); #if LL_DARWIN @@ -225,70 +288,92 @@ namespace tut ALScriptKeymap::chords_t rest = ALScriptKeymap::menuKeys("next_tab"); rest.erase(std::remove(rest.begin(), rest.end(), ALKeyChord{ KEY_TAB, REAL_CONTROL }), rest.end()); from = ALScriptKeymap::takeKeys(revert, ALKeyChord{ KEY_TAB, REAL_CONTROL }, true); - ensure("one of several keys: the rest kept", named(from, "next_tab") && !rest.empty() && ALScriptKeymap::menuKeys("next_tab") == rest); + { + INFO("one of several keys: the rest kept"); + CHECK(named(from, "next_tab")); + REQUIRE_FALSE(rest.empty()); + CHECK(ALScriptKeymap::menuKeys("next_tab") == rest); + } ALScriptKeymap::restoreAll(); ALScriptKeymap::Owner save_all; save_all.menu = "save_all"; - ensure("its own keys given again: nobody's", ALScriptKeymap::takeKeys(save_all, ALKeyChord{ 'S', MASK_NONE, 'K', MASK_CONTROL }, false).empty()); + UNSCOPED_INFO("its own keys given again: nobody's"); + REQUIRE(ALScriptKeymap::takeKeys(save_all, ALKeyChord{ 'S', MASK_NONE, 'K', MASK_CONTROL }, false).empty()); ALScriptKeymap::Owner find; find.command = C::Find; - ensure("nor an editor's own", ALScriptKeymap::takeKeys(find, ALKeyChord{ 'F', MASK_CONTROL }, false).empty()); + UNSCOPED_INFO("nor an editor's own"); + REQUIRE(ALScriptKeymap::takeKeys(find, ALKeyChord{ 'F', MASK_CONTROL }, false).empty()); } - template<> template<> - void alscriptkeymap_object::test<6>() + TEST_CASE_METHOD(alscriptkeymap_data, "alscriptkeymap: the zoom keys are the usual Control ones and back and forward avoid them", "[alscriptkeymap]") { - set_test_name("the text's size on Control-=, Control-minus and Control-0, as everywhere; back and forward off them"); const auto has = [](const char* id, const ALKeyChord& chord) { const ALScriptKeymap::chords_t keys = ALScriptKeymap::menuKeys(id); return std::find(keys.begin(), keys.end(), chord) != keys.end(); }; - ensure("zoom in, the '=' key as Windows and SDL say it", has("zoom_in", ALKeyChord{ '=', MASK_CONTROL }) && has("zoom_in", ALKeyChord{ KEY_EQUALS, MASK_CONTROL })); - ensure("and Control-plus", has("zoom_in", ALKeyChord{ '=', MASK_CONTROL | MASK_SHIFT }) && has("zoom_in", ALKeyChord{ KEY_ADD, MASK_CONTROL })); - ensure("zoom out", has("zoom_out", ALKeyChord{ '-', MASK_CONTROL })); - ensure("the size chosen", has("zoom_reset", ALKeyChord{ '0', MASK_CONTROL })); - ensure("back not on Control-minus", !has("back", ALKeyChord{ '-', MASK_CONTROL })); + { + INFO("zoom in, the '=' key as Windows and SDL say it"); + CHECK(has("zoom_in", ALKeyChord{ '=', MASK_CONTROL })); + CHECK(has("zoom_in", ALKeyChord{ KEY_EQUALS, MASK_CONTROL })); + } + { + INFO("and Control-plus"); + CHECK(has("zoom_in", ALKeyChord{ '=', MASK_CONTROL | MASK_SHIFT })); + CHECK(has("zoom_in", ALKeyChord{ KEY_ADD, MASK_CONTROL })); + } + UNSCOPED_INFO("zoom out"); + CHECK(has("zoom_out", ALKeyChord{ '-', MASK_CONTROL })); + UNSCOPED_INFO("the size chosen"); + CHECK(has("zoom_reset", ALKeyChord{ '0', MASK_CONTROL })); + UNSCOPED_INFO("back not on Control-minus"); + CHECK_FALSE(has("back", ALKeyChord{ '-', MASK_CONTROL })); #if LL_DARWIN - ensure("back on the Mac's Control-minus", has("back", ALKeyChord{ '-', MASK_MAC_CONTROL })); + UNSCOPED_INFO("back on the Mac's Control-minus"); + CHECK(has("back", ALKeyChord{ '-', MASK_MAC_CONTROL })); #else - ensure("back on Alt-Left", has("back", ALKeyChord{ KEY_LEFT, MASK_ALT }) && has("forward", ALKeyChord{ KEY_RIGHT, MASK_ALT })); + UNSCOPED_INFO("back on Alt-Left and forward on Alt-Right"); + CHECK((has("back", ALKeyChord{ KEY_LEFT, MASK_ALT }) && has("forward", ALKeyChord{ KEY_RIGHT, MASK_ALT }))); #endif } - template<> template<> - void alscriptkeymap_object::test<7>() + TEST_CASE_METHOD(alscriptkeymap_data, "alscriptkeymap: every preset binds studio commands once and editor ones to single keys", "[alscriptkeymap]") { - set_test_name("every preset names the studio's commands, each once, an editor's command with keys of one key each"); - ensure("the studio's own first, changing nothing", ALScriptKeyPresets::all().front().id == ALScriptKeyPresets::STANDARD && - ALScriptKeyPresets::all().front().bindings.empty()); - ensure("four", ALScriptKeyPresets::all().size() == 4); + { + INFO("the studio's own first, changing nothing"); + CHECK(ALScriptKeyPresets::all().front().id == ALScriptKeyPresets::STANDARD); + REQUIRE(ALScriptKeyPresets::all().front().bindings.empty()); + } + REQUIRE(ALScriptKeyPresets::all().size() == 4); for (const ALScriptKeyPresets::Preset& preset : ALScriptKeyPresets::all()) { std::set named; for (const ALScriptKeyPresets::Binding& binding : preset.bindings) { const std::string what = preset.id + ": " + binding.id; - ensure("once: " + what, named.insert(binding.id).second); - ensure("keys: " + what, !binding.keys.empty()); + UNSCOPED_INFO("once: " << what); + REQUIRE(named.insert(binding.id).second); + UNSCOPED_INFO("keys: " << what); + REQUIRE_FALSE(binding.keys.empty()); const bool editor = alEditorCommandFromName(binding.id).has_value(); - ensure("a command: " + what, editor || ALScriptKeymap::isMenuCommand(binding.id)); + UNSCOPED_INFO("a command: " << what); + REQUIRE((editor || ALScriptKeymap::isMenuCommand(binding.id))); for (const ALKeyChord& chord : binding.keys) { - ensure("one key at a time for an editor's: " + what, !editor || !chord.twoKeys()); + UNSCOPED_INFO("one key at a time for an editor's: " << what); + REQUIRE((!editor || !chord.twoKeys())); } } } } - template<> template<> - void alscriptkeymap_object::test<8>() + TEST_CASE_METHOD(alscriptkeymap_data, "alscriptkeymap: under each preset every key reaches its command and none is shared", "[alscriptkeymap]") { - set_test_name("with each preset in force, every key it gives is its command's, and no key is two commands'"); for (const ALScriptKeyPresets::Preset& preset : ALScriptKeyPresets::all()) { ALScriptKeymap::setPreset(preset.id); - ensure_equals("in force", ALScriptKeymap::preset(), preset.id); + UNSCOPED_INFO("in force"); + REQUIRE(ALScriptKeymap::preset() == preset.id); const ALKeymap& map = ALScriptKeymap::current(); for (const ALScriptKeyPresets::Binding& binding : preset.bindings) { @@ -297,13 +382,14 @@ namespace tut { for (const ALKeyChord& chord : binding.keys) { - ensure("the editor's key: " + what + " " + chord.describe(), map.lookup(chord.key, chord.mask) == *command); + UNSCOPED_INFO("the editor's key: " << what << " " << chord.describe()); + REQUIRE(map.lookup(chord.key, chord.mask) == *command); } } else { - ensure("the menu's keys: " + what + " " + ALScriptKeymap::describe(ALScriptKeymap::menuKeys(binding.id)), - ALScriptKeymap::menuKeys(binding.id) == binding.keys); + UNSCOPED_INFO("the menu's keys: " << what << " " << ALScriptKeymap::describe(ALScriptKeymap::menuKeys(binding.id))); + REQUIRE(ALScriptKeymap::menuKeys(binding.id) == binding.keys); } } // No menu's key the first key of an editor's command, nor @@ -315,7 +401,8 @@ namespace tut const std::string what = preset.id + ": " + id + " " + chord.describe(); const KEY first_key = chord.twoKeys() ? chord.leadKey : chord.key; const MASK first_mask = chord.twoKeys() ? chord.leadMask : chord.mask; - ensure("not an editor's key: " + what, map.lookup(first_key, first_mask) == ALEditorCommand::None); + UNSCOPED_INFO("not an editor's key: " << what); + REQUIRE(map.lookup(first_key, first_mask) == ALEditorCommand::None); for (const std::string& other : ALScriptKeymap::menuIds()) { if (other == id) @@ -326,7 +413,8 @@ namespace tut { const bool clash = theirs == chord || (!chord.twoKeys() && theirs.ledBy(chord.key, chord.mask)) || (chord.twoKeys() && !theirs.twoKeys() && theirs.key == chord.leadKey && theirs.mask == chord.leadMask); - ensure("not " + other + "'s too: " + what, !clash); + UNSCOPED_INFO("not " << other << "'s too: " << what); + REQUIRE_FALSE(clash); } } } @@ -334,36 +422,45 @@ namespace tut } } - template<> template<> - void alscriptkeymap_object::test<9>() + TEST_CASE_METHOD(alscriptkeymap_data, "alscriptkeymap: a person's keys win over any preset and restoring keeps the preset", "[alscriptkeymap]") { - set_test_name("a person's own keys stay over another preset and win; restoring one goes back to the preset's, and all keeps the preset"); #if LL_DARWIN const std::string other_preset = "xcode"; #else const std::string other_preset = "visual_studio"; #endif ALScriptKeymap::setPreset("jetbrains"); - ensure("JetBrains' Control-D duplicates", ALScriptKeymap::current().lookup('D', MASK_CONTROL) == C::DuplicateLine); + UNSCOPED_INFO("JetBrains' Control-D duplicates"); + CHECK(ALScriptKeymap::current().lookup('D', MASK_CONTROL) == C::DuplicateLine); ALScriptKeymap::takeKeys(ALScriptKeymap::Owner{ C::JoinLines, "" }, ALKeyChord{ 'D', MASK_CONTROL }, true); ALScriptKeymap::rebind(C::JoinLines, { { 'D', MASK_CONTROL } }); - ensure("taken", ALScriptKeymap::current().lookup('D', MASK_CONTROL) == C::JoinLines && ALScriptKeymap::isRebound(C::JoinLines)); + { + INFO("taken"); + CHECK(ALScriptKeymap::current().lookup('D', MASK_CONTROL) == C::JoinLines); + CHECK(ALScriptKeymap::isRebound(C::JoinLines)); + } ALScriptKeymap::setPreset(other_preset); - ensure("kept over another preset", ALScriptKeymap::current().lookup('D', MASK_CONTROL) == C::JoinLines); - ensure("and the person's still", ALScriptKeymap::anyRebound()); + UNSCOPED_INFO("kept over another preset"); + CHECK(ALScriptKeymap::current().lookup('D', MASK_CONTROL) == C::JoinLines); + UNSCOPED_INFO("and the person's still"); + CHECK(ALScriptKeymap::anyRebound()); ALScriptKeymap::restore(C::JoinLines); ALScriptKeymap::restore(C::DuplicateLine); - ensure("restored: the preset's own", ALScriptKeymap::current().lookup('D', MASK_CONTROL) == C::DuplicateLine); + UNSCOPED_INFO("restored: the preset's own"); + CHECK(ALScriptKeymap::current().lookup('D', MASK_CONTROL) == C::DuplicateLine); ALScriptKeymap::restoreAll(); - ensure("all restored, the preset kept", ALScriptKeymap::preset() == other_preset && !ALScriptKeymap::anyRebound()); + { + INFO("all restored, the preset kept"); + CHECK(ALScriptKeymap::preset() == other_preset); + CHECK_FALSE(ALScriptKeymap::anyRebound()); + } ALScriptKeymap::setPreset("nothing of the kind"); - ensure("one not offered: the standard", ALScriptKeymap::preset() == ALScriptKeyPresets::STANDARD); + UNSCOPED_INFO("one not offered: the standard"); + CHECK(ALScriptKeymap::preset() == ALScriptKeyPresets::STANDARD); } - template<> template<> - void alscriptkeymap_object::test<10>() + TEST_CASE_METHOD(alscriptkeymap_data, "alscriptkeymap: comparison bar commands are Alt with a first letter and peeking is Alt-F3", "[alscriptkeymap]") { - set_test_name("what the comparison's bar does has keys too, the first letter of each word; peeking at a change on Alt-F3, beside Alt-F5's step"); const std::pair keys[] = { { "compare_settle_theirs", ALKeyChord{ 'T', MASK_ALT } }, { "compare_settle_mine", ALKeyChord{ 'M', MASK_ALT } }, { "compare_settle_both", ALKeyChord{ 'B', MASK_ALT } }, { "compare_fold", ALKeyChord{ 'F', MASK_ALT } }, @@ -372,24 +469,30 @@ namespace tut }; for (const auto& [id, chord] : keys) { - ensure(std::string("a menu command: ") + id, ALScriptKeymap::isMenuCommand(id)); - ensure(std::string("its key: ") + id + " " + chord.describe(), ALScriptKeymap::menuKey(id) == chord); - ensure(std::string("no editor's: ") + id, ALScriptKeymap::current().lookup(chord.key, chord.mask) == C::None); + UNSCOPED_INFO("a menu command: " << id); + REQUIRE(ALScriptKeymap::isMenuCommand(id)); + UNSCOPED_INFO("its key: " << id << " " << chord.describe()); + REQUIRE(ALScriptKeymap::menuKey(id) == chord); + UNSCOPED_INFO("no editor's: " << id); + REQUIRE(ALScriptKeymap::current().lookup(chord.key, chord.mask) == C::None); } - ensure("the step beside it", ALScriptKeymap::menuKey("next_difference") == (ALKeyChord{ KEY_F5, MASK_ALT })); + UNSCOPED_INFO("the step beside it"); + CHECK(ALScriptKeymap::menuKey("next_difference") == (ALKeyChord{ KEY_F5, MASK_ALT })); } - template<> template<> - void alscriptkeymap_object::test<11>() + TEST_CASE_METHOD(alscriptkeymap_data, "alscriptkeymap: keys kept for a menu command now gone take nothing from others", "[alscriptkeymap]") { - set_test_name("keys kept for a menu's command the table no longer has, renamed or gone, take nothing from the commands that have them"); // Go > Next Change was next_change until it was told from Next Edit // Location, the editor's command of that name. - ensure("no menu command now", !ALScriptKeymap::isMenuCommand("next_change")); + UNSCOPED_INFO("no menu command now"); + CHECK_FALSE(ALScriptKeymap::isMenuCommand("next_change")); ALScriptKeymap::rebindMenu("next_change", { ALKeyChord{ KEY_F5, MASK_ALT }, ALKeyChord{ KEY_F8, MASK_NONE } }); - ensure("the step's key its own still", ALScriptKeymap::menuKey("next_difference") == (ALKeyChord{ KEY_F5, MASK_ALT })); - ensure("and a problem's", has(ALScriptKeymap::menuKeys("next_problem"), ALKeyChord{ KEY_F8, MASK_NONE })); - ensure("the editor's command of that name untouched", ALScriptKeymap::keysOf(ALScriptKeymap::current(), C::NextChange).empty() == + UNSCOPED_INFO("the step's key its own still"); + CHECK(ALScriptKeymap::menuKey("next_difference") == (ALKeyChord{ KEY_F5, MASK_ALT })); + UNSCOPED_INFO("and a problem's"); + CHECK(has(ALScriptKeymap::menuKeys("next_problem"), ALKeyChord{ KEY_F8, MASK_NONE })); + UNSCOPED_INFO("the editor's command of that name untouched"); + REQUIRE(ALScriptKeymap::keysOf(ALScriptKeymap::current(), C::NextChange).empty() == ALScriptKeymap::keysOf(ALKeymap::standard(), C::NextChange).empty()); ALScriptKeymap::restoreAll(); } diff --git a/indra/newview/tests/alscriptlookup_test.cpp b/indra/newview/tests/alscriptlookup_test.cpp index 1997e4ac15..446e97c222 100644 --- a/indra/newview/tests/alscriptlookup_test.cpp +++ b/indra/newview/tests/alscriptlookup_test.cpp @@ -30,6 +30,8 @@ #include "../alscriptlookup.h" #include "alincludeidentity.h" +#include "almainloop.h" +#include "alteststateguard.h" #include "llfile.h" #include "workqueue.h" @@ -38,7 +40,7 @@ #include "llfocusmgr.h" #include "newview_test_fakes.h" -#include "../test/lltut.h" +#include #include #include @@ -318,7 +320,7 @@ namespace bool has(const std::string& text, const std::string& part) { return text.find(part) != std::string::npos; } } -namespace tut +namespace { struct alscriptlookup_data { @@ -357,7 +359,7 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } navigation = std::make_unique(services); unit = std::make_unique(services, studio, studio, navigation->unit, studio); @@ -392,19 +394,14 @@ namespace tut } }; - typedef test_group alscriptlookup_group; - typedef alscriptlookup_group::object alscriptlookup_object; - alscriptlookup_group alscriptlookup_instance("alscriptlookup"); - - template<> template<> - void alscriptlookup_object::test<1>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: places kept once and sorted own first and language words reserved", "[alscriptlookup]") { - set_test_name("places once each, the script's own first then each other's by name; the language's own words reserved"); Doc::Lookup lookup; ALScriptLookup::addPlace(lookup, place("", 1, 2)); ALScriptLookup::addPlace(lookup, place("", 1, 2, 7)); ALScriptLookup::addPlace(lookup, place("x", 1, 2)); - ensure_equals("once a file and a start", lookup.places.size(), size_t(2)); + UNSCOPED_INFO("once a file and a start"); + CHECK(lookup.places.size() == size_t(2)); std::vector places = { place("object:z", 0, 0, 5, "Beta"), place("", 3, 0, 5, "Zeta"), place("object:y", 2, 0, 5, "Alpha"), place("object:x", 0, 0, 5, "Beta"), @@ -415,82 +412,130 @@ namespace tut { order.push_back(each.file + ":" + std::to_string(each.span.line)); } - ensure_equals("own whatever it is called, then by name, file and place", joined(order), + UNSCOPED_INFO("own whatever it is called, then by name, file and place"); + CHECK(joined(order) == joined(Names{ ":1", ":3", "object:y:1", "object:y:2", "object:x:0", "object:z:0" })); - ensure("an LSL keyword", ALScriptLookup::reserved(false, "state") && !ALScriptLookup::reserved(true, "state")); - ensure("a Luau keyword", ALScriptLookup::reserved(true, "local") && !ALScriptLookup::reserved(false, "local")); - ensure("the preprocessor's, in LSL", ALScriptLookup::reserved(false, "switch") && !ALScriptLookup::reserved(true, "switch")); - ensure("the definitions'", ALScriptLookup::reserved(false, "llSay") && ALScriptLookup::reserved(true, "ll.Say")); - ensure("a name of the script's", !ALScriptLookup::reserved(false, "count") && !ALScriptLookup::reserved(true, "count")); + { + INFO("an LSL keyword"); + CHECK(ALScriptLookup::reserved(false, "state")); + CHECK_FALSE(ALScriptLookup::reserved(true, "state")); + } + { + INFO("a Luau keyword"); + CHECK(ALScriptLookup::reserved(true, "local")); + CHECK_FALSE(ALScriptLookup::reserved(false, "local")); + } + { + INFO("the preprocessor's, in LSL"); + CHECK(ALScriptLookup::reserved(false, "switch")); + CHECK_FALSE(ALScriptLookup::reserved(true, "switch")); + } + { + INFO("the definitions'"); + CHECK(ALScriptLookup::reserved(false, "llSay")); + CHECK(ALScriptLookup::reserved(true, "ll.Say")); + } + { + INFO("a name of the script's"); + CHECK_FALSE(ALScriptLookup::reserved(false, "count")); + CHECK_FALSE(ALScriptLookup::reserved(true, "count")); + } } - template<> template<> - void alscriptlookup_object::test<2>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: a name declared nowhere shared is found in the script alone at once", "[alscriptlookup]") { - set_test_name("a name declared nowhere shared looked up in the script alone, answered at once and shown, the preview held"); make(); studio.others = { { b, "B" } }; tab("a", a, A_TEXT, "A").preview = true; Doc& doc = lookUp(ALEditorCommand::FindReferences, false); - ensure("the preview held", !doc.preview); - ensure("nothing else asked", studio.asked.empty() && studio.loads.empty()); - ensure_equals("shown", studio.shows, 1); - ensure_equals("in its tab", studio.shownIn, std::string("a")); - ensure_equals("from", studio.shown.from, std::string("a")); - ensure_equals("named", studio.shown.fromName, std::string("A")); - ensure_equals("the name", studio.shown.name, std::string("count")); - ensure("in order", studio.shown.places.size() == 2 && studio.shown.places[0].span.line == 0); - ensure("not declared", !studio.shown.hasDefinition); - ensure("said", !services.statuses.empty() && has(services.statuses.back(), "ReferencesFound [COUNT]=2")); - ensure("done with", doc.lookup->command == ALEditorCommand::None && doc.lookup->pending == 0); + UNSCOPED_INFO("the preview held"); + CHECK_FALSE(doc.preview); + { + INFO("nothing else asked"); + CHECK(studio.asked.empty()); + CHECK(studio.loads.empty()); + } + CHECK(studio.shows == 1); + CHECK(studio.shownIn == std::string("a")); + CHECK(studio.shown.from == std::string("a")); + CHECK(studio.shown.fromName == std::string("A")); + CHECK(studio.shown.name == std::string("count")); + { + INFO("in order"); + REQUIRE(studio.shown.places.size() == 2); + CHECK(studio.shown.places[0].span.line == 0); + } + CHECK_FALSE(studio.shown.hasDefinition); + REQUIRE_FALSE(services.statuses.empty()); + CHECK(has(services.statuses.back(), "ReferencesFound [COUNT]=2")); + { + INFO("done with"); + CHECK(doc.lookup->command == ALEditorCommand::None); + CHECK(doc.lookup->pending == 0); + } } - template<> template<> - void alscriptlookup_object::test<3>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: the object's other scripts are read and expanded and asked for places", "[alscriptlookup]") { - set_test_name("the object's others: one open without the name passed over, one read, expanded, asked, and its place added"); make(); tab("c", c, "default { touch_start(integer n) { } }\n", "C"); studio.others = { { c, "C" }, { b, "B" } }; Doc& doc = lookUp(ALEditorCommand::FindReferences); - ensure("candidates asked for", studio.asked == Names{ "a" }); - ensure_equals("one to read", studio.loads.size(), size_t(1)); - ensure("the open one passed over", studio.expands.empty()); - ensure("waiting said", has(services.statuses.back(), "LookingAcross [COUNT]=1 [NAME]=count")); - ensure_equals("nothing shown yet", studio.shows, 0); + UNSCOPED_INFO("candidates asked for"); + CHECK(studio.asked == Names{ "a" }); + REQUIRE(studio.loads.size() == size_t(1)); + UNSCOPED_INFO("the open one passed over"); + CHECK(studio.expands.empty()); + UNSCOPED_INFO("waiting said"); + CHECK(has(services.statuses.back(), "LookingAcross [COUNT]=1 [NAME]=count")); + UNSCOPED_INFO("nothing shown yet"); + CHECK(studio.shows == 0); const LLUUID asset("eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee"); studio.loads[0].loaded(asset, B_TEXT); - ensure_equals("expanded", studio.expands.size(), size_t(1)); + REQUIRE(studio.expands.size() == size_t(1)); const ALScriptPreprocessor::Request& request = studio.expands[0].request; - ensure("as the compiler sees it, not optimized", - request.ref == b && request.name == "B" && request.assetId == asset && request.sourceText() == B_TEXT && !request.optimize); + { + INFO("as the compiler sees it, not optimized"); + CHECK(request.ref == b); + CHECK(request.name == "B"); + CHECK(request.assetId == asset); + CHECK(request.sourceText() == B_TEXT); + CHECK_FALSE(request.optimize); + } studio.expands[0].expanded( expansion("B", ALScriptPreprocessor::pathOf(b), B_TEXT, "A", ALScriptPreprocessor::pathOf(a), "integer count;\n")); - ensure_equals("asked", studio.asks.size(), size_t(1)); + REQUIRE(studio.asks.size() == size_t(1)); const ALScriptAnalysis::Request& asked = studio.asks[0].request; - ensure("where the declaration went", asked.kind == ALScriptAnalysis::Kind::References && asked.line == 0 && asked.column == 8); - ensure_equals("by who asked and the script", asked.id, "lookup:" + doc.id + ":" + b.id()); - ensure_equals("of this lookup", asked.version, doc.lookup->generation); + { + INFO("where the declaration went"); + CHECK(asked.kind == ALScriptAnalysis::Kind::References); + CHECK(asked.line == 0); + CHECK(asked.column == 8); + } + UNSCOPED_INFO("by who asked and the script"); + CHECK(asked.id == "lookup:" + doc.id + ":" + b.id()); + UNSCOPED_INFO("of this lookup"); + CHECK(asked.version == doc.lookup->generation); studio.asks[0].answered(answer({ span(0, 8, 5), span(1, 8, 5) })); - ensure_equals("shown", studio.shows, 1); - ensure_equals("its own through the other's expansion left out", studio.shown.places.size(), size_t(3)); + CHECK(studio.shows == 1); + UNSCOPED_INFO("its own through the other's expansion left out"); + REQUIRE(studio.shown.places.size() == size_t(3)); const Doc::Place& other = studio.shown.places[2]; - ensure_equals("in B", other.file, ALScriptPreprocessor::pathOf(b)); - ensure_equals("named", other.fileName, std::string("B")); - ensure_equals("as written", other.text, std::string("foo() { count = 2; }")); - ensure_equals("the name in it", other.at, 8); - ensure("declared", studio.shown.hasDefinition); - ensure("across scripts", has(services.statuses.back(), "ReferencesFoundAcross [COUNT]=3 [FILES]=2")); + CHECK(other.file == ALScriptPreprocessor::pathOf(b)); + CHECK(other.fileName == std::string("B")); + CHECK(other.text == std::string("foo() { count = 2; }")); + UNSCOPED_INFO("the name in it"); + CHECK(other.at == 8); + CHECK(studio.shown.hasDefinition); + CHECK(has(services.statuses.back(), "ReferencesFoundAcross [COUNT]=3 [FILES]=2")); } - template<> template<> - void alscriptlookup_object::test<4>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: an open script is expanded from its tab and an include's line read once", "[alscriptlookup]") { - set_test_name("an open script with the name answered on the spot, the others still counted; an include's line where it is had"); make(); Doc& open = tab("b", b, B_TEXT, "B"); studio.others = { { b, "B" }, { c, "C" } }; @@ -498,53 +543,87 @@ namespace tut studio.lines[inc + "#0"] = " integer count; // kept"; Doc& doc = services.findDoc("a") ? *services.findDoc("a") : tab("a", a, A_TEXT, "A"); unit->start(doc, ALEditorCommand::FindReferences, refsOf("count"), true, inc, span(0, 8, 5), {}, doc.editor->document().version()); - ensure("the open one expanded at once", studio.expands.size() == 1 && studio.expands[0].request.sourceText() == open.editor->text()); - ensure("from its tab's own copy, not another", studio.expands[0].request.source == open.snapshot()); - ensure_equals("the other read", studio.loads.size(), size_t(1)); - ensure("both waited for", doc.lookup->pending == 2 && studio.shows == 0); + { + INFO("the open one expanded at once"); + REQUIRE(studio.expands.size() == 1); + CHECK(studio.expands[0].request.sourceText() == open.editor->text()); + } + UNSCOPED_INFO("from its tab's own copy, not another"); + CHECK(studio.expands[0].request.source == open.snapshot()); + UNSCOPED_INFO("the other read"); + REQUIRE(studio.loads.size() == size_t(1)); + { + INFO("both waited for"); + CHECK(doc.lookup->pending == 2); + CHECK(studio.shows == 0); + } studio.expands[0].expanded(expansion("B", ALScriptPreprocessor::pathOf(b), B_TEXT, "inc.lsl", inc, "integer count;\n")); studio.asks[0].answered(answer({ span(0, 8, 5) })); - ensure("still one to come", studio.shows == 0 && doc.lookup->pending == 1); + { + INFO("still one to come"); + CHECK(studio.shows == 0); + CHECK(doc.lookup->pending == 1); + } studio.loads[0].loaded(LLUUID::null, ""); - ensure_equals("an empty one passed over", studio.shows, 1); - ensure_equals("one place", studio.shown.places.size(), size_t(1)); + UNSCOPED_INFO("an empty one passed over"); + CHECK(studio.shows == 1); + REQUIRE(studio.shown.places.size() == size_t(1)); const Doc::Place& found = studio.shown.places[0]; - ensure("in the include", found.file == inc && found.fileName == "inc.lsl"); - ensure("as the include reads", found.text == "integer count; // kept" && found.at == 5); - ensure_equals("its lines asked for once", studio.linesAsked[inc], 1); + { + INFO("in the include"); + CHECK(found.file == inc); + CHECK(found.fileName == "inc.lsl"); + } + { + INFO("as the include reads"); + CHECK(found.text == "integer count; // kept"); + CHECK(found.at == 5); + } + UNSCOPED_INFO("its lines asked for once"); + CHECK(studio.linesAsked[inc] == 1); studio.lines.clear(); unit->start(doc, ALEditorCommand::FindReferences, refsOf("count"), true, inc, span(0, 8, 5), {}, doc.editor->document().version()); studio.expands.back().expanded(expansion("B", ALScriptPreprocessor::pathOf(b), B_TEXT, "inc.lsl", inc, "integer count;\n")); studio.asks.back().answered(answer({ span(0, 8, 5) })); studio.loads.back().loaded(LLUUID::null, "default { }\n"); - ensure_equals("shown again", studio.shows, 2); - ensure("the expansion's line, the name not placed in it", - studio.shown.places.size() == 1 && studio.shown.places[0].text == "integer count;" && studio.shown.places[0].at == -1); + UNSCOPED_INFO("shown again"); + CHECK(studio.shows == 2); + { + INFO("the expansion's line, the name not placed in it"); + REQUIRE(studio.shown.places.size() == 1); + CHECK(studio.shown.places[0].text == "integer count;"); + CHECK(studio.shown.places[0].at == -1); + } } - template<> template<> - void alscriptlookup_object::test<5>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: answers to an earlier lookup or after the unit is gone are dropped", "[alscriptlookup]") { - set_test_name("answers to an earlier lookup, or after the lookups are gone, dropped; a script not naming the home passed over"); make(); studio.others = { { b, "B" } }; Doc& doc = lookUp(ALEditorCommand::FindReferences); lookUp(ALEditorCommand::FindReferences, false); - ensure_equals("the second shown", studio.shows, 1); + UNSCOPED_INFO("the second shown"); + CHECK(studio.shows == 1); studio.loads[0].loaded(LLUUID::null, B_TEXT); - ensure("the first's answer dropped", studio.expands.empty() && studio.shows == 1); + { + INFO("the first's answer dropped"); + CHECK(studio.expands.empty()); + CHECK(studio.shows == 1); + } lookUp(ALEditorCommand::FindReferences); studio.loads.back().loaded(LLUUID::null, B_TEXT); studio.expands.back().expanded(expansion("B", ALScriptPreprocessor::pathOf(b), B_TEXT)); - ensure("no declaration in it: nothing asked", studio.asks.empty()); - ensure_equals("shown without it", studio.shows, 2); + UNSCOPED_INFO("no declaration in it: nothing asked"); + CHECK(studio.asks.empty()); + UNSCOPED_INFO("shown without it"); + CHECK(studio.shows == 2); const U32 generation = doc.lookup->generation; lookUp(ALEditorCommand::FindReferences); - ensure("a new generation", doc.lookup->generation != generation); + CHECK(doc.lookup->generation != generation); studio.loads.back().loaded(LLUUID::null, B_TEXT); // A lookup of another tab's, still waiting on its read, as this // one waits on its expansion: both current, and both gone with it. @@ -555,38 +634,52 @@ namespace tut unit.reset(); studio.expands.back().expanded( expansion("B", ALScriptPreprocessor::pathOf(b), B_TEXT, "A", ALScriptPreprocessor::pathOf(a), "integer count;\n")); - ensure("nothing asked once gone", studio.asks.empty() && studio.shows == 2); + { + INFO("nothing asked once gone"); + CHECK(studio.asks.empty()); + CHECK(studio.shows == 2); + } studio.loads.back().loaded(LLUUID::null, B_TEXT); - ensure("nothing read once gone", studio.expands.size() == expands); + UNSCOPED_INFO("nothing read once gone"); + CHECK(studio.expands.size() == expands); } - template<> template<> - void alscriptlookup_object::test<6>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: Rename says what is not renamable and hints at what return will do", "[alscriptlookup]") { - set_test_name("Rename: one that may not be said so; else the new name asked, the row under it saying what return will do"); make(); lookUp(ALEditorCommand::Rename, false, false); - ensure("not renamable", has(services.statuses.back(), "NotRenamable") && studio.askedName.empty()); + { + INFO("not renamable"); + CHECK(has(services.statuses.back(), "NotRenamable")); + CHECK(studio.askedName.empty()); + } Doc& doc = lookUp(ALEditorCommand::Rename, false); - ensure_equals("asked in its tab", studio.askedName, std::string("a")); + UNSCOPED_INFO("asked in its tab"); + CHECK(studio.askedName == std::string("a")); ALScriptOutlineEntry entry; entry.name = "total"; doc.outline.push_back(entry); - ensure("nothing typed", has(studio.hint(""), "RenameHint [COUNT]=2 [FILES]=1 [NAME]=count")); - ensure("no name", has(studio.hint("9lives"), "RenameBadName") && has(studio.hint("9lives"), "[NAME]=9lives")); - ensure("reserved", has(studio.hint("state"), "RenameReserved [NAME]=state")); - ensure("the same", has(studio.hint(" count "), "RenameSame")); - ensure("a clash said, allowed", has(studio.hint("total"), "RenameClash")); - ensure("what it will do", has(studio.hint("sum"), "RenameTo [COUNT]=2") && has(studio.hint("sum"), "[NEW]=sum")); + UNSCOPED_INFO("nothing typed"); + CHECK(has(studio.hint(""), "RenameHint [COUNT]=2 [FILES]=1 [NAME]=count")); + CHECK(has(studio.hint("9lives"), "RenameBadName")); + CHECK(has(studio.hint("9lives"), "[NAME]=9lives")); + CHECK(has(studio.hint("state"), "RenameReserved [NAME]=state")); + CHECK(has(studio.hint(" count "), "RenameSame")); + UNSCOPED_INFO("a clash said, allowed"); + CHECK(has(studio.hint("total"), "RenameClash")); + { + INFO("what it will do"); + CHECK(has(studio.hint("sum"), "RenameTo [COUNT]=2")); + CHECK(has(studio.hint("sum"), "[NEW]=sum")); + } services.docs.clear(); - ensure("its tab gone: nothing said", studio.hint("sum").empty()); + UNSCOPED_INFO("its tab gone: nothing said"); + CHECK(studio.hint("sum").empty()); } - template<> template<> - void alscriptlookup_object::test<7>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: a rename across scripts edits open tabs and opens others with it pending", "[alscriptlookup]") { - set_test_name("a rename across scripts: here as one step, open ones unchanged since, others opened with it waiting, the rest left"); make(); Doc& doc = tab("a", a, A_TEXT, "A"); Doc& open = tab("b", b, "foo() { count = 2; }\nbar() { total = count; }\n", "B"); @@ -604,135 +697,209 @@ namespace tut place(pc, 0, 8, 5, "C"), place(pd, 2, 4, 5, "D"), place("disk:/e.lsl", 0, 8, 5, "e.lsl"), place("weird:x", 0, 0) }, doc.editor->document().version()); moved.editor->insertText("// "); - ensure("asked", (bool)studio.chosen); - ensure("across scripts", has(studio.hint("sum"), "RenameToAcross [COUNT]=9 [FILES]=6")); + UNSCOPED_INFO("asked"); + REQUIRE((bool)studio.chosen); + CHECK(has(studio.hint("sum"), "RenameToAcross [COUNT]=9 [FILES]=6")); studio.chosen("9lives"); - ensure("no name", has(services.statuses.back(), "RenameBadName") && doc.editor->text() == A_TEXT); + CHECK(has(services.statuses.back(), "RenameBadName")); + CHECK(doc.editor->text() == A_TEXT); studio.chosen("state"); - ensure("reserved", has(services.statuses.back(), "RenameReserved")); + CHECK(has(services.statuses.back(), "RenameReserved")); studio.chosen("count"); - ensure("the same: nothing", services.reports.empty() && doc.editor->text() == A_TEXT); + { + INFO("the same: nothing"); + CHECK(services.reports.empty()); + CHECK(doc.editor->text() == A_TEXT); + } studio.chosen(" total "); - ensure_equals("here", doc.editor->text(), std::string("integer total;\ndefault { state_entry() { total = 1; } }\n")); - ensure_equals("one step", doc.editor->undoJournal().undoLabel(), std::string("rename")); - ensure_equals("open and unchanged: where it still stands", open.editor->text(), + UNSCOPED_INFO("here"); + CHECK(doc.editor->text() == std::string("integer total;\ndefault { state_entry() { total = 1; } }\n")); + UNSCOPED_INFO("one step"); + CHECK(doc.editor->undoJournal().undoLabel() == std::string("rename")); + UNSCOPED_INFO("open and unchanged: where it still stands"); + CHECK(open.editor->text() == std::string("foo() { total = 2; }\nbar() { total = total; }\n")); - ensure_equals("there as one step too", open.editor->undoJournal().undoLabel(), std::string("rename")); - ensure("moved on since: left", has(moved.editor->text(), "count") && !has(moved.editor->text(), "total")); - ensure("not open: opened", services.opened.size() == 1 && services.opened[0].ref == d && services.opened[0].name == "D"); + UNSCOPED_INFO("there as one step too"); + CHECK(open.editor->undoJournal().undoLabel() == std::string("rename")); + { + INFO("moved on since: left"); + CHECK(has(moved.editor->text(), "count")); + CHECK_FALSE(has(moved.editor->text(), "total")); + } + { + INFO("not open: opened"); + REQUIRE(services.opened.size() == 1); + CHECK(services.opened[0].ref == d); + CHECK(services.opened[0].name == "D"); + } Doc* waiting = services.findDoc("d"); - ensure("the change waiting", waiting && waiting->pendingEdits.size() == 1 && waiting->pendingEdits[0].now == "total"); - ensure("a file opened", studio.filesOpened == Names{ "/e.lsl" }); - ensure_equals("and changed", services.findDoc("disk:/e.lsl")->editor->text(), std::string("integer total = 4;\n")); - ensure_equals("said once", services.reports.size(), size_t(1)); + { + INFO("the change waiting"); + REQUIRE(waiting); + REQUIRE(waiting->pendingEdits.size() == 1); + CHECK(waiting->pendingEdits[0].now == "total"); + } + UNSCOPED_INFO("a file opened"); + CHECK(studio.filesOpened == Names{ "/e.lsl" }); + UNSCOPED_INFO("and changed"); + CHECK(services.findDoc("disk:/e.lsl")->editor->text() == std::string("integer total = 4;\n")); + UNSCOPED_INFO("said once"); + REQUIRE(services.reports.size() == size_t(1)); const al_studio_test::FakeServices::Said& said = services.reports[0]; - ensure("the counts", has(said.text, "RenamedIn [NAME]=total [PLACES]=Places [COUNT]=6 [SCRIPTS]=Scripts [COUNT]=4")); - ensure("opened, a clause joined on", has(said.text, "[SECOND]=RenamedOpened [NAME]=total [PLACES]=Places [COUNT]=6 [SCRIPTS]=Scripts [COUNT]=2")); - ensure("left, another", has(said.text, "[SECOND]=RenamedLeft [NAME]=total [PLACES]=Places [COUNT]=6 [SCRIPTS]=Scripts [COUNT]=2")); - ensure("as the language joins clauses and ends a sentence", has(said.text, "Sentence [TEXT]=JoinClauses [FIRST]=JoinClauses")); - ensure("a failure where any was left", said.failure); - ensure("its tab brought forward", studio.activated == Names{ "a" }); + CHECK(has(said.text, "RenamedIn [NAME]=total [PLACES]=Places [COUNT]=6 [SCRIPTS]=Scripts [COUNT]=4")); + UNSCOPED_INFO("opened, a clause joined on"); + CHECK(has(said.text, "[SECOND]=RenamedOpened [NAME]=total [PLACES]=Places [COUNT]=6 [SCRIPTS]=Scripts [COUNT]=2")); + UNSCOPED_INFO("left, another"); + CHECK(has(said.text, "[SECOND]=RenamedLeft [NAME]=total [PLACES]=Places [COUNT]=6 [SCRIPTS]=Scripts [COUNT]=2")); + UNSCOPED_INFO("as the language joins clauses and ends a sentence"); + CHECK(has(said.text, "Sentence [TEXT]=JoinClauses [FIRST]=JoinClauses")); + UNSCOPED_INFO("a failure where any was left"); + CHECK(said.failure); + UNSCOPED_INFO("its tab brought forward"); + CHECK(studio.activated == Names{ "a" }); } - template<> template<> - void alscriptlookup_object::test<8>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: a stale rename is refused and one in this script alone is said as here", "[alscriptlookup]") { - set_test_name("a rename of a script that has moved on since the lookup refused, and one in only this script said as here"); make(); Doc& doc = lookUp(ALEditorCommand::Rename, false); doc.editor->insertText(" "); studio.chosen("total"); - ensure("stale", has(services.statuses.back(), "RenameStale [NAME]=total") && services.reports.empty()); + CHECK(has(services.statuses.back(), "RenameStale [NAME]=total")); + CHECK(services.reports.empty()); lookUp(ALEditorCommand::Rename, false); studio.chosen("total"); - ensure_equals("said", services.reports.size(), size_t(1)); - ensure("here", has(services.reports[0].text, "RenamedHere") && !has(services.reports[0].text, ";")); - ensure("not a failure", !services.reports[0].failure); + REQUIRE(services.reports.size() == size_t(1)); + { + INFO("here"); + CHECK(has(services.reports[0].text, "RenamedHere")); + CHECK_FALSE(has(services.reports[0].text, ";")); + } + CHECK_FALSE(services.reports[0].failure); Doc& open = tab("b", b, "foo() { count = 2; }\n", "B"); doc.editor->setText(A_TEXT); unit->start(doc, ALEditorCommand::Rename, refsOf("count"), false, std::string(), span(0, 8, 5), { place("", 0, 8), place(ALScriptPreprocessor::pathOf(b), 0, 8, 5, "B") }, doc.editor->document().version()); studio.chosen("sum"); - ensure("in both, none opened", has(services.reports.back().text, "RenamedIn") && !has(services.reports.back().text, ";")); - ensure_equals("B too", open.editor->text(), std::string("foo() { sum = 2; }\n")); + { + INFO("in both, none opened"); + CHECK(has(services.reports.back().text, "RenamedIn")); + CHECK_FALSE(has(services.reports.back().text, ";")); + } + UNSCOPED_INFO("B too"); + CHECK(open.editor->text() == std::string("foo() { sum = 2; }\n")); } - template<> template<> - void alscriptlookup_object::test<9>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: pending edits are made where the old name stands and the rest said missed", "[alscriptlookup]") { - set_test_name("edits waiting for a text made where the old name stands, the rest said missed; none where nothing may change"); make(); Doc& doc = tab("d", d, "integer count;\nx = count;\n", "D"); doc.loaded = false; doc.pendingEdits = { { span(0, 8, 5), "count", "total" }, { span(1, 0, 5), "count", "total" } }; unit->applyPendingEdits(doc); - ensure("not loaded: kept", doc.pendingEdits.size() == 2 && doc.editor->text() == "integer count;\nx = count;\n"); + { + INFO("not loaded: kept"); + CHECK(doc.pendingEdits.size() == 2); + CHECK(doc.editor->text() == "integer count;\nx = count;\n"); + } doc.loaded = true; doc.editor->setReadOnly(true); unit->applyPendingEdits(doc); - ensure("made", doc.pendingEdits.empty() && doc.editor->text() == "integer total;\nx = count;\n"); - ensure_equals("one step", doc.editor->undoJournal().undoLabel(), std::string("rename")); - ensure("the one missed said", services.reports.size() == 1 && has(services.reports[0].text, "RenameMissed [COUNT]=1 [NAME]=D")); - ensure("of the tab, a failure", services.reports[0].doc == "d" && services.reports[0].failure); + { + INFO("made"); + CHECK(doc.pendingEdits.empty()); + CHECK(doc.editor->text() == "integer total;\nx = count;\n"); + } + UNSCOPED_INFO("one step"); + CHECK(doc.editor->undoJournal().undoLabel() == std::string("rename")); + { + INFO("the one missed said"); + REQUIRE(services.reports.size() == 1); + CHECK(has(services.reports[0].text, "RenameMissed [COUNT]=1 [NAME]=D")); + } + { + INFO("of the tab, a failure"); + CHECK(services.reports[0].doc == "d"); + CHECK(services.reports[0].failure); + } Doc::PendingEdit replace{ span(0, 8, 5), "total", "sum", true }; doc.pendingEdits = { replace }; unit->applyPendingEdits(doc); - ensure("a replace", doc.editor->text() == "integer sum;\nx = count;\n" && doc.editor->undoJournal().undoLabel() == "replace"); - ensure("nothing missed", services.reports.size() == 1); + { + INFO("a replace"); + CHECK(doc.editor->text() == "integer sum;\nx = count;\n"); + CHECK(doc.editor->undoJournal().undoLabel() == "replace"); + } + UNSCOPED_INFO("nothing missed"); + CHECK(services.reports.size() == 1); doc.modifiable = false; doc.pendingEdits = { { span(0, 8, 3), "sum", "total", true } }; unit->applyPendingEdits(doc); - ensure("none made", doc.editor->text() == "integer sum;\nx = count;\n"); - ensure("every one missed", services.reports.size() == 2 && has(services.reports[1].text, "ReplaceMissed [COUNT]=1")); + UNSCOPED_INFO("none made"); + CHECK(doc.editor->text() == "integer sum;\nx = count;\n"); + { + INFO("every one missed"); + REQUIRE(services.reports.size() == 2); + CHECK(has(services.reports[1].text, "ReplaceMissed [COUNT]=1")); + } } - template<> template<> - void alscriptlookup_object::test<10>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: other scripts are read a few at a time and a new lookup drops the rest", "[alscriptlookup]") { - set_test_name("the other scripts read a few at a time, the next begun as one is done with; a lookup begun again lets the rest go"); make(); const ALScriptRef e{ object, LLUUID("eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee") }; const ALScriptRef f{ object, LLUUID("ffffffff-ffff-ffff-ffff-ffffffffffff") }; studio.others = { { b, "B" }, { c, "C" }, { d, "D" }, { e, "E" }, { f, "F" } }; Doc& doc = lookUp(ALEditorCommand::FindReferences); - ensure_equals("no more than so many read at once", studio.loads.size(), size_t(ALScriptLookup::AT_ONCE)); - ensure_equals("every one waited for", doc.lookup->pending, S32(5)); + UNSCOPED_INFO("no more than so many read at once"); + REQUIRE(studio.loads.size() == size_t(ALScriptLookup::AT_ONCE)); + UNSCOPED_INFO("every one waited for"); + CHECK(doc.lookup->pending == S32(5)); // One that does not name it: passed over, and the next read. studio.loads[0].loaded(LLUUID::null, "default { }\n"); - ensure_equals("the next begun", studio.loads.size(), size_t(ALScriptLookup::AT_ONCE + 1)); - ensure_equals("one fewer waited for", doc.lookup->pending, S32(4)); + UNSCOPED_INFO("the next begun"); + REQUIRE(studio.loads.size() == size_t(ALScriptLookup::AT_ONCE + 1)); + UNSCOPED_INFO("one fewer waited for"); + CHECK(doc.lookup->pending == S32(4)); studio.loads[1].loaded(LLUUID::null, ""); studio.loads[2].loaded(LLUUID::null, ""); - ensure_equals("and on", studio.loads.size(), size_t(5)); + UNSCOPED_INFO("and on"); + REQUIRE(studio.loads.size() == size_t(5)); studio.loads[3].loaded(LLUUID::null, ""); studio.loads[4].loaded(LLUUID::null, ""); - ensure("all done with: shown", doc.lookup->pending == 0 && studio.shows == 1); + { + INFO("all done with: shown"); + CHECK(doc.lookup->pending == 0); + CHECK(studio.shows == 1); + } // Begun again with some still to read: what the first had not // begun is let go of, and its answers dropped. studio.loads.clear(); lookUp(ALEditorCommand::FindReferences); - ensure_equals("two read", studio.loads.size(), size_t(2)); + UNSCOPED_INFO("two read"); + REQUIRE(studio.loads.size() == size_t(2)); lookUp(ALEditorCommand::FindReferences); - ensure_equals("two more, for the new one", studio.loads.size(), size_t(4)); + UNSCOPED_INFO("two more, for the new one"); + REQUIRE(studio.loads.size() == size_t(4)); studio.loads[0].loaded(LLUUID::null, ""); studio.loads[1].loaded(LLUUID::null, ""); - ensure_equals("the first's answers begin nothing", studio.loads.size(), size_t(4)); + UNSCOPED_INFO("the first's answers begin nothing"); + REQUIRE(studio.loads.size() == size_t(4)); studio.loads[2].loaded(LLUUID::null, ""); - ensure_equals("the new one's go on", studio.loads.size(), size_t(5)); + UNSCOPED_INFO("the new one's go on"); + CHECK(studio.loads.size() == size_t(5)); } - template<> template<> - void alscriptlookup_object::test<11>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: folderCandidates are the folder's other scripts in the same language", "[alscriptlookup]") { - set_test_name("an inventory script's lookups reach the others of its folder in its language: SLua by subtype or runtime, not notecards, not itself"); const auto made = [](const char* name, LLAssetType::EType type, U32 subtype, const char* runtime) { LLPointer item = new LLInventoryItem(); item->setUUID(LLUUID::generateNewID()); @@ -750,106 +917,174 @@ namespace tut const std::vector folder = { own.get(), lib.get(), lua.get(), onluau.get(), readme.get() }; const std::vector lsl = ALScriptLookup::folderCandidates(folder, own->getUUID(), false); - ensure("LSL: the other LSL script", lsl.size() == 1 && lsl[0].name == "lib.lsl" && lsl[0].ref.inInventory() && lsl[0].ref.item == lib->getUUID()); + { + INFO("LSL: the other LSL script"); + REQUIRE(lsl.size() == 1); + CHECK(lsl[0].name == "lib.lsl"); + CHECK(lsl[0].ref.inInventory()); + CHECK(lsl[0].ref.item == lib->getUUID()); + } const std::vector slua = ALScriptLookup::folderCandidates(folder, lua->getUUID(), true); - ensure("SLua: by runtime too", slua.size() == 1 && slua[0].name == "port.luau"); + { + INFO("SLua: by runtime too"); + REQUIRE(slua.size() == 1); + CHECK(slua[0].name == "port.luau"); + } } - template<> template<> - void alscriptlookup_object::test<12>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: a previewed rename is made at the places kept and clashes only with code", "[alscriptlookup]") { - set_test_name("Rename previewed: Shift-Return shows each place, made at those kept; a name anywhere in what it changes is a clash; an include's other scripts said out of reach"); make(); lookUp(ALEditorCommand::Rename, false); - ensure("previewing offered", (bool)studio.previewed); - ensure("the prompt says Shift-Return shows it first", has(studio.hint("sum"), "RenamePreviewHint")); + UNSCOPED_INFO("previewing offered"); + REQUIRE((bool)studio.previewed); + UNSCOPED_INFO("the prompt says Shift-Return shows it first"); + CHECK(has(studio.hint("sum"), "RenamePreviewHint")); studio.previewed("9lives"); - ensure("no name: nothing shown", has(services.statuses.back(), "RenameBadName") && !studio.apply); + { + INFO("no name: nothing shown"); + CHECK(has(services.statuses.back(), "RenameBadName")); + CHECK_FALSE(studio.apply); + } studio.previewed("sum"); - ensure("shown from its tab", studio.previewIn == "a" && studio.previewName == "sum" && studio.previewOf.places.size() == 2); - ensure("what it will do said over them", has(studio.previewSaid, "RenameTo [COUNT]=2") && !has(studio.previewSaid, "RenamePreviewHint")); + { + INFO("shown from its tab"); + CHECK(studio.previewIn == "a"); + CHECK(studio.previewName == "sum"); + REQUIRE(studio.previewOf.places.size() == 2); + } + { + INFO("what it will do said over them"); + CHECK(has(studio.previewSaid, "RenameTo [COUNT]=2")); + CHECK_FALSE(has(studio.previewSaid, "RenamePreviewHint")); + } studio.apply({ 1 }); Doc& doc = *services.findDoc("a"); - ensure_equals("made at the one kept", doc.editor->text(), std::string("integer count;\ndefault { state_entry() { sum = 1; } }\n")); + UNSCOPED_INFO("made at the one kept"); + CHECK(doc.editor->text() == std::string("integer count;\ndefault { state_entry() { sum = 1; } }\n")); // A local of the same name -- in no outline -- is a clash; a name in // a comment or a string is not. doc.editor->setText("integer count;\ndefault { state_entry() { integer total = count; llSay(0, \"hidden\"); } } // gone\n"); lookUp(ALEditorCommand::Rename, false); - ensure("a local's name", has(studio.hint("total"), "RenameClash") && has(studio.hint("total"), "[SCRIPT]=A")); - ensure("not a string's", !has(studio.hint("hidden"), "RenameClash")); - ensure("nor a comment's", !has(studio.hint("gone"), "RenameClash")); + { + INFO("a local's name"); + CHECK(has(studio.hint("total"), "RenameClash")); + CHECK(has(studio.hint("total"), "[SCRIPT]=A")); + } + UNSCOPED_INFO("not a string's"); + CHECK_FALSE(has(studio.hint("hidden"), "RenameClash")); + UNSCOPED_INFO("nor a comment's"); + CHECK_FALSE(has(studio.hint("gone"), "RenameClash")); // A place in an include: the scripts elsewhere that include it are // not reached, and said to be so. unit->start(doc, ALEditorCommand::Rename, refsOf("count"), true, "disk:/lib.lsl", span(0, 8, 5), { place("", 1, 26), place("disk:/lib.lsl", 0, 8, 5, "lib.lsl") }, doc.editor->document().version()); - ensure("out of reach said", has(studio.hint("sum"), "RenameUnreached [NAMES]=lib.lsl")); - - ensure("SLua: a comment is not a mention", !ALScriptLookup::mentions("local a = 1 -- total\n", "total", true)); - ensure("nor a long comment", !ALScriptLookup::mentions("--[[ total ]] local b = 2", "total", true)); - ensure("nor a long string", !ALScriptLookup::mentions("local s = [==[ total ]==]", "total", true)); - ensure("a local is", ALScriptLookup::mentions("local total = 1", "total", true)); - ensure("not part of a longer name", !ALScriptLookup::mentions("integer totals;", "total", false)); - ensure("LSL: nor in a block comment", !ALScriptLookup::mentions("/* total */ integer x;", "total", false)); + UNSCOPED_INFO("out of reach said"); + CHECK(has(studio.hint("sum"), "RenameUnreached [NAMES]=lib.lsl")); + + UNSCOPED_INFO("SLua: a comment is not a mention"); + CHECK_FALSE(ALScriptLookup::mentions("local a = 1 -- total\n", "total", true)); + UNSCOPED_INFO("nor a long comment"); + CHECK_FALSE(ALScriptLookup::mentions("--[[ total ]] local b = 2", "total", true)); + UNSCOPED_INFO("nor a long string"); + CHECK_FALSE(ALScriptLookup::mentions("local s = [==[ total ]==]", "total", true)); + UNSCOPED_INFO("a local is"); + CHECK(ALScriptLookup::mentions("local total = 1", "total", true)); + UNSCOPED_INFO("not part of a longer name"); + CHECK_FALSE(ALScriptLookup::mentions("integer totals;", "total", false)); + UNSCOPED_INFO("LSL: nor in a block comment"); + CHECK_FALSE(ALScriptLookup::mentions("/* total */ integer x;", "total", false)); } - template<> template<> - void alscriptlookup_object::test<13>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: the object's scripts wait on its prims and what was missed is said", "[alscriptlookup]") { - set_test_name("an object's others taken once its prims have said what they hold; the prims that did not, and a script not read, said with what was found"); make(); studio.others = { { b, "B" }, { c, "C" } }; studio.unlisted = 2; studio.holdCandidates = true; lookUp(ALEditorCommand::FindReferences); - ensure("asked", studio.asked == Names{ "a" }); - ensure("nothing read while the prims are asked", studio.loads.empty()); - ensure_equals("nothing shown", studio.shows, 0); + CHECK(studio.asked == Names{ "a" }); + UNSCOPED_INFO("nothing read while the prims are asked"); + CHECK(studio.loads.empty()); + CHECK(studio.shows == 0); studio.heldCandidates(); - ensure_equals("both read", studio.loads.size(), size_t(2)); + UNSCOPED_INFO("both read"); + REQUIRE(studio.loads.size() == size_t(2)); studio.loads[0].loaded(LLUUID::null, std::nullopt); - ensure_equals("one still out", studio.shows, 0); + UNSCOPED_INFO("one still out"); + CHECK(studio.shows == 0); studio.loads[1].loaded(LLUUID::null, std::string("default { }\n")); - ensure_equals("shown", studio.shows, 1); + CHECK(studio.shows == 1); const std::string said = services.statuses.back(); - ensure("found, then what was passed over: " + said, - has(said, "ReferencesFound") && has(said, "LookupUnlisted [COUNT]=2") && has(said, "LookupUnread [COUNT]=1 [NAMES]=B")); + { + INFO("found, then what was passed over: " + said); + CHECK(has(said, "ReferencesFound")); + CHECK(has(said, "LookupUnlisted [COUNT]=2")); + CHECK(has(said, "LookupUnread [COUNT]=1 [NAMES]=B")); + } // A rename says it under the prompt, all prims listed this time. studio.holdCandidates = false; studio.unlisted = 0; studio.loads.clear(); lookUp(ALEditorCommand::Rename); - ensure_equals("read again", studio.loads.size(), size_t(2)); + UNSCOPED_INFO("read again"); + REQUIRE(studio.loads.size() == size_t(2)); studio.loads[0].loaded(LLUUID::null, std::nullopt); studio.loads[1].loaded(LLUUID::null, std::string("default { }\n")); - ensure("a new name asked", static_cast(studio.hint)); + UNSCOPED_INFO("a new name asked"); + REQUIRE(static_cast(studio.hint)); const std::string hint = studio.hint("total"); - ensure("the hint says it: " + hint, has(hint, "LookupUnread [COUNT]=1 [NAMES]=B") && !has(hint, "LookupUnlisted")); + { + INFO("the hint says it: " + hint); + CHECK(has(hint, "LookupUnread [COUNT]=1 [NAMES]=B")); + CHECK_FALSE(has(hint, "LookupUnlisted")); + } } - template<> template<> - void alscriptlookup_object::test<14>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: ALScriptPlaces::Lines gives lines without returns and none past the end", "[alscriptlookup]") { - set_test_name("a text's lines found once: held, read where it is, or open in a tab; none past the end; a line's return left off"); const ALScriptPlaces::Lines held(std::make_shared("one\r\ntwo\n\nfour")); - ensure("four lines", held.has(0) && held.has(3) && !held.has(4) && !held.has(-1)); - ensure("each as it is", held.line(0) == "one" && held.line(1) == "two" && held.line(2).empty() && held.line(3) == "four"); - ensure("none past the end", held.line(9).empty()); + { + INFO("four lines"); + CHECK(held.has(0)); + CHECK(held.has(3)); + CHECK_FALSE(held.has(4)); + CHECK_FALSE(held.has(-1)); + } + { + INFO("each as it is"); + CHECK(held.line(0) == "one"); + CHECK(held.line(1) == "two"); + CHECK(held.line(2).empty()); + CHECK(held.line(3) == "four"); + } + UNSCOPED_INFO("none past the end"); + CHECK(held.line(9).empty()); const std::string text = "a\nb\n"; const ALScriptPlaces::Lines here(text); - ensure("read where it is, the last line empty", here.line(1) == "b" && here.has(2) && here.line(2).empty() && !here.has(3)); + { + INFO("read where it is, the last line empty"); + CHECK(here.line(1) == "b"); + CHECK(here.has(2)); + CHECK(here.line(2).empty()); + CHECK_FALSE(here.has(3)); + } const ALTextDocument open("x\ny"); const ALScriptPlaces::Lines tab(&open); - ensure("a tab's", tab.line(1) == "y" && !tab.has(2)); - ensure("nothing: no lines", !ALScriptPlaces::Lines().has(0)); + { + INFO("a tab's"); + CHECK(tab.line(1) == "y"); + CHECK_FALSE(tab.has(2)); + } + UNSCOPED_INFO("nothing: no lines"); + CHECK_FALSE(ALScriptPlaces::Lines().has(0)); } - template<> template<> - void alscriptlookup_object::test<15>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: a rename preview compares an open unchanged file with its renamed text", "[alscriptlookup]") { - set_test_name("a rename previewed shows what it makes of a file, at the places kept: here, or in another open and unchanged; not in one that is not"); make(); Doc& doc = tab("a", a, A_TEXT, "A"); Doc& open = tab("b", b, "foo() { count = 2; }\nbar() { total = count; }\n", "B"); @@ -858,33 +1093,45 @@ namespace tut { place("", 0, 8), place("", 1, 26), place(pb, 0, 8, 5, "B"), place(pb, 1, 16, 5, "B"), place(pd, 2, 4, 5, "D") }, doc.editor->document().version()); studio.previewed("sum"); - ensure("offered", (bool)studio.changes); + REQUIRE((bool)studio.changes); studio.changes("", { 1, 2, 3 }); - ensure_equals("here, at the one of its own kept", studio.compared.size(), 1U); - ensure_equals("beside it as it is", studio.compared.back(), + UNSCOPED_INFO("here, at the one of its own kept"); + REQUIRE(studio.compared.size() == 1U); + UNSCOPED_INFO("beside it as it is"); + CHECK(studio.compared.back() == "a: " + std::string(A_TEXT) + " | integer count;\ndefault { state_entry() { sum = 1; } }\n (CompareNow | RenameAfter [NAME]=sum [SCRIPT]=A)"); - ensure("in its tab, brought forward", studio.activated.back() == "a"); - ensure("nothing changed", doc.editor->text() == A_TEXT); + UNSCOPED_INFO("in its tab, brought forward"); + CHECK(studio.activated.back() == "a"); + UNSCOPED_INFO("nothing changed"); + CHECK(doc.editor->text() == A_TEXT); studio.changes(pb, { 2, 3 }); - ensure_equals("another open", studio.compared.back(), + UNSCOPED_INFO("another open"); + CHECK(studio.compared.back() == "b: foo() { count = 2; }\nbar() { total = count; }\n | foo() { sum = 2; }\nbar() { total = sum; }\n (CompareNow | RenameAfter [NAME]=sum [SCRIPT]=B)"); - ensure("in its tab", studio.activated.back() == "b"); + UNSCOPED_INFO("in its tab"); + CHECK(studio.activated.back() == "b"); const size_t before = studio.compared.size(); studio.changes(pd, { 4 }); - ensure("not open: said, not shown", studio.compared.size() == before && has(services.statuses.back(), "RenameChangesNotHere [NAME]=sum [SCRIPT]=D") && - services.statusFailures.back()); + { + INFO("not open: said, not shown"); + CHECK(studio.compared.size() == before); + CHECK(has(services.statuses.back(), "RenameChangesNotHere [NAME]=sum [SCRIPT]=D")); + CHECK(services.statusFailures.back()); + } open.editor->insertText("// "); studio.changes(pb, { 2, 3 }); - ensure("changed since: said", studio.compared.size() == before && has(services.statuses.back(), "RenameChangesNotHere")); + { + INFO("changed since: said"); + CHECK(studio.compared.size() == before); + CHECK(has(services.statuses.back(), "RenameChangesNotHere")); + } } - template<> template<> - void alscriptlookup_object::test<16>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: open files on disk are looked through and renamed with the script", "[alscriptlookup]") { - set_test_name("files on disk open here are looked through too, which no object lists: a module's field renamed in the script, the module and another script requiring it"); make(); const std::string LIB = "disk:/s/lib.luau"; const std::string OTHER = "disk:/s/b.luau"; @@ -904,40 +1151,65 @@ namespace tut // module's declaration. unit->start(doc, ALEditorCommand::Rename, refsOf("twice"), true, LIB, span(1, 11, 5), { place("", 1, 10), place(LIB, 1, 11, 5, "lib.luau") }, doc.editor->document().version()); - ensure_equals("each open file of the language, as it stands in its tab", studio.expands.size(), size_t(2)); + UNSCOPED_INFO("each open file of the language, as it stands in its tab"); + REQUIRE(studio.expands.size() == size_t(2)); const ALScriptPreprocessor::Request& module = studio.expands[0].request; - ensure("the module, by its path, no item", module.path == LIB && module.ref.isNull() && module.name == "lib.luau" && - module.sourceText() == LIB_TEXT); - ensure("the other script", studio.expands[1].request.path == OTHER && studio.expands[1].request.sourceText() == B_LUA); + { + INFO("the module, by its path, no item"); + CHECK(module.path == LIB); + CHECK(module.ref.isNull()); + CHECK(module.name == "lib.luau"); + CHECK(module.sourceText() == LIB_TEXT); + } + CHECK(studio.expands[1].request.path == OTHER); + CHECK(studio.expands[1].request.sourceText() == B_LUA); // The module read alone is the home itself; the other script has it // ahead of its own lines. studio.expands[0].expanded(expansion("lib.luau", LIB, LIB_TEXT)); studio.expands[1].expanded(expansion("b.luau", OTHER, B_LUA, "lib.luau", LIB, LIB_TEXT)); - ensure_equals("both asked", studio.asks.size(), size_t(2)); - ensure("the module at its declaration", studio.asks[0].request.line == 1 && studio.asks[0].request.column == 11 && - studio.asks[0].request.id == "lookup:a:" + LIB); - ensure("the other where the module's declaration went", studio.asks[1].request.line == 1 && studio.asks[1].request.column == 11); + UNSCOPED_INFO("both asked"); + REQUIRE(studio.asks.size() == size_t(2)); + { + INFO("the module at its declaration"); + CHECK(studio.asks[0].request.line == 1); + CHECK(studio.asks[0].request.column == 11); + CHECK(studio.asks[0].request.id == "lookup:a:" + LIB); + } + { + INFO("the other where the module's declaration went"); + CHECK(studio.asks[1].request.line == 1); + CHECK(studio.asks[1].request.column == 11); + } studio.asks[0].answered(answer({ span(1, 11, 5) })); studio.asks[1].answered(answer({ span(1, 11, 5), span(4, 10, 5) })); - ensure("asked for the new name", (bool)studio.chosen); - ensure_equals("each place once: the script's, the module's, the other's", doc.lookup->places.size(), size_t(3)); - ensure("the other's, by its path", std::any_of(doc.lookup->places.begin(), doc.lookup->places.end(), [&](const Doc::Place& place) { + UNSCOPED_INFO("asked for the new name"); + REQUIRE((bool)studio.chosen); + UNSCOPED_INFO("each place once: the script's, the module's, the other's"); + CHECK(doc.lookup->places.size() == size_t(3)); + UNSCOPED_INFO("the other's, by its path"); + CHECK(std::any_of(doc.lookup->places.begin(), doc.lookup->places.end(), [&](const Doc::Place& place) { return place.file == OTHER && place.fileName == "b.luau" && place.span.line == 1 && place.span.column == 10; })); studio.chosen("double"); - ensure_equals("here", doc.editor->text(), std::string("local lib = require(\"./lib\")\nprint(lib.double(2))\n")); - ensure_equals("in the module", lib.editor->text(), std::string("local M = {}\nfunction M.double(n) return n * 2 end\nreturn M\n")); - ensure_equals("in the other script", other.editor->text(), std::string("local lib = require(\"./lib\")\nprint(lib.double(4))\n")); - ensure("each its own step", lib.editor->undoJournal().undoLabel() == "rename" && other.editor->undoJournal().undoLabel() == "rename"); - ensure_equals("another language's file left alone", lsl.editor->text(), std::string("integer twice;\n")); + UNSCOPED_INFO("here"); + CHECK(doc.editor->text() == std::string("local lib = require(\"./lib\")\nprint(lib.double(2))\n")); + UNSCOPED_INFO("in the module"); + CHECK(lib.editor->text() == std::string("local M = {}\nfunction M.double(n) return n * 2 end\nreturn M\n")); + UNSCOPED_INFO("in the other script"); + CHECK(other.editor->text() == std::string("local lib = require(\"./lib\")\nprint(lib.double(4))\n")); + { + INFO("each its own step"); + CHECK(lib.editor->undoJournal().undoLabel() == "rename"); + CHECK(other.editor->undoJournal().undoLabel() == "rename"); + } + UNSCOPED_INFO("another language's file left alone"); + CHECK(lsl.editor->text() == std::string("integer twice;\n")); } - template<> template<> - void alscriptlookup_object::test<17>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: scripts on disk in the script's folders are read and opened to be renamed", "[alscriptlookup]") { - set_test_name("scripts on disk under the folders a script reads from are looked through, open or not: one not open read as it is on disk, and opened to be renamed"); make(); // A folder of the scripter's, as the window lists it, where it // stands: what the walk names each file by. @@ -964,15 +1236,25 @@ namespace tut doc.language.lua = true; unit->start(doc, ALEditorCommand::Rename, refsOf("twice"), true, LIB, span(1, 11, 5), { place("", 1, 10), place(LIB, 1, 11, 5, "lib.luau") }, doc.editor->document().version()); - ensure_equals("the module open here once, the other read from disk, and the one not naming it passed over", studio.expands.size(), size_t(2)); - ensure("the module, as its tab has it", studio.expands[0].request.path == LIB && studio.expands[0].request.sourceText() == LIB_TEXT); - ensure("the other, as it is on disk", studio.expands[1].request.path == B && studio.expands[1].request.name == "b.luau" && - studio.expands[1].request.sourceText() == B_LUA); + UNSCOPED_INFO("the module open here once, the other read from disk, and the one not naming it passed over"); + REQUIRE(studio.expands.size() == size_t(2)); + { + INFO("the module, as its tab has it"); + CHECK(studio.expands[0].request.path == LIB); + CHECK(studio.expands[0].request.sourceText() == LIB_TEXT); + } + { + INFO("the other, as it is on disk"); + CHECK(studio.expands[1].request.path == B); + CHECK(studio.expands[1].request.name == "b.luau"); + CHECK(studio.expands[1].request.sourceText() == B_LUA); + } studio.expands[0].expanded(expansion("lib.luau", LIB, LIB_TEXT)); studio.expands[1].expanded(expansion("b.luau", B, B_LUA, "lib.luau", LIB, LIB_TEXT)); studio.asks[0].answered(answer({ span(1, 11, 5) })); studio.asks[1].answered(answer({ span(1, 11, 5), span(4, 10, 5) })); - ensure("the other's place, by its path", std::any_of(doc.lookup->places.begin(), doc.lookup->places.end(), [&](const Doc::Place& place) { + UNSCOPED_INFO("the other's place, by its path"); + CHECK(std::any_of(doc.lookup->places.begin(), doc.lookup->places.end(), [&](const Doc::Place& place) { return place.file == B && place.span.line == 1 && place.span.column == 10; })); @@ -984,17 +1266,20 @@ namespace tut return &opened; }; studio.chosen("double"); - ensure("opened to be renamed", studio.filesOpened == Names{ b_file }); + UNSCOPED_INFO("opened to be renamed"); + CHECK(studio.filesOpened == Names{ b_file }); const Doc* other = services.findDoc(B); - ensure("renamed there", other && other->editor->text() == "local lib = require(\"./lib\")\nprint(lib.double(4))\n"); + { + INFO("renamed there"); + REQUIRE(other); + CHECK(other->editor->text() == "local lib = require(\"./lib\")\nprint(lib.double(4))\n"); + } std::error_code ec; fs::remove_all(root, ec); } - template<> template<> - void alscriptlookup_object::test<18>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: SLua read apart asks in the module and maps places back through the piece", "[alscriptlookup]") { - set_test_name("SLua read apart, as the checker reads it: another script asked at the module's declaration in the module, with its modules, and each place read back through its own piece"); make(); const std::string LIB = "disk:/s/lib.luau"; const std::string OTHER = "disk:/s/b.luau"; @@ -1007,29 +1292,46 @@ namespace tut doc.language.lua = true; unit->start(doc, ALEditorCommand::FindReferences, refsOf("twice"), true, LIB, span(1, 11, 5), { place("", 1, 10), place(LIB, 1, 11, 5, "lib.luau") }, doc.editor->document().version()); - ensure_equals("the other expanded", studio.expands.size(), size_t(1)); - ensure("apart asked for", studio.expands[0].request.apart); + UNSCOPED_INFO("the other expanded"); + REQUIRE(studio.expands.size() == size_t(1)); + UNSCOPED_INFO("apart asked for"); + CHECK(studio.expands[0].request.apart); studio.expands[0].expanded(apartExpansion("b.luau", OTHER, B_LUA, "./lib", "lib.luau", LIB, LIB_TEXT)); - ensure_equals("asked", studio.asks.size(), size_t(1)); + REQUIRE(studio.asks.size() == size_t(1)); const ALScriptAnalysis::Request& asked = studio.asks[0].request; - ensure("the script alone, its require a call", asked.text && *asked.text == B_LUA); - ensure("in the module, at its declaration in its own lines", asked.module == LIB && asked.line == 1 && asked.column == 11); - ensure("with its modules", asked.modules && asked.modules->modules.size() == 1 && asked.modules->modules[0].key == LIB && - asked.modules->reaches.size() == 1 && asked.modules->reaches[0].name == "./lib"); + { + INFO("the script alone, its require a call"); + REQUIRE(asked.text); + CHECK(*asked.text == B_LUA); + } + { + INFO("in the module, at its declaration in its own lines"); + CHECK(asked.module == LIB); + CHECK(asked.line == 1); + CHECK(asked.column == 11); + } + { + INFO("with its modules"); + REQUIRE(asked.modules); + REQUIRE(asked.modules->modules.size() == 1); + CHECK(asked.modules->modules[0].key == LIB); + REQUIRE(asked.modules->reaches.size() == 1); + CHECK(asked.modules->reaches[0].name == "./lib"); + } ALScriptAnalysis::Result result = answer({ span(1, 10, 5) }); result.references.elsewhere.push_back({ LIB, span(1, 11, 5) }); studio.asks[0].answered(result); - ensure("the other's place, through its piece", std::any_of(doc.lookup->places.begin(), doc.lookup->places.end(), [&](const Doc::Place& place) { + UNSCOPED_INFO("the other's place, through its piece"); + CHECK(std::any_of(doc.lookup->places.begin(), doc.lookup->places.end(), [&](const Doc::Place& place) { return place.file == OTHER && place.fileName == "b.luau" && place.span.line == 1 && place.span.column == 10; })); - ensure_equals("each once: the script's, the module's, the other's", doc.lookup->places.size(), size_t(3)); + UNSCOPED_INFO("each once: the script's, the module's, the other's"); + CHECK(doc.lookup->places.size() == size_t(3)); } - template<> template<> - void alscriptlookup_object::test<19>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: a file open under another path to it is read once as its tab has it", "[alscriptlookup]") { - set_test_name("a file open here by another way to its path is the one file: looked through once, as its tab has it, and found to be where the name is declared"); make(); namespace fs = std::filesystem; const fs::path made = fs::temp_directory_path() / ("alscriptlookup_" + LLUUID::generateNewID().asString()); @@ -1053,20 +1355,26 @@ namespace tut doc.language.lua = true; unit->start(doc, ALEditorCommand::FindReferences, refsOf("twice"), true, LIB, span(1, 11, 5), { place("", 1, 10), place(LIB, 1, 11, 5, "lib.luau") }, doc.editor->document().version()); - ensure_equals("the module once, and the other", studio.expands.size(), size_t(2)); - ensure("the module as its tab has it", studio.expands[0].request.path == LIB_TAB && studio.expands[0].request.sourceText() == TYPED); - ensure("the other from disk", studio.expands[1].request.sourceText() == B_LUA); + UNSCOPED_INFO("the module once, and the other"); + REQUIRE(studio.expands.size() == size_t(2)); + { + INFO("the module as its tab has it"); + CHECK(studio.expands[0].request.path == LIB_TAB); + CHECK(studio.expands[0].request.sourceText() == TYPED); + } + UNSCOPED_INFO("the other from disk"); + CHECK(studio.expands[1].request.sourceText() == B_LUA); studio.expands[0].expanded(expansion("lib.luau", LIB_TAB, TYPED)); - ensure_equals("the home itself, asked at its declaration", studio.asks.size(), size_t(1)); - ensure("there", studio.asks[0].request.line == 1 && studio.asks[0].request.column == 11); + UNSCOPED_INFO("the home itself, asked at its declaration"); + REQUIRE(studio.asks.size() == size_t(1)); + CHECK(studio.asks[0].request.line == 1); + CHECK(studio.asks[0].request.column == 11); std::error_code ec; fs::remove_all(root, ec); } - template<> template<> - void alscriptlookup_object::test<20>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: folders on disk are walked off the main thread and stale walks dropped", "[alscriptlookup]") { - set_test_name("with a main loop to hand them back to, the folders on disk are walked and read off the main thread, the lookup held open for them; nothing comes of a walk once the lookups are gone"); make(); namespace fs = std::filesystem; const fs::path made = fs::temp_directory_path() / ("alscriptlookup_" + LLUUID::generateNewID().asString()); @@ -1075,9 +1383,11 @@ namespace tut const std::string B_LUA = "local lib = require(\"./lib\")\nprint(lib.twice(4))\n"; llofstream((root / "b.luau").string(), std::ios::binary) << B_LUA; studio.disk = { root.string() }; - // The main loop's queue, as the viewer's is: kept, since a walk - // let go of may hand back to it after the test. - LL::WorkQueue& main_loop = newview_test::mainLoop(); + // A main loop's queue, as the viewer's is, chosen for this test + // alone: the queue kept, since a walk let go of may hand back to it + // after the test. + LL::WorkQueue& main_loop = newview_test::mainLoop(); + const ALMainLoop::Choice chosen(&main_loop); const auto pumped = [&main_loop](const std::function& until, std::chrono::milliseconds within) { const auto deadline = std::chrono::steady_clock::now() + within; while (std::chrono::steady_clock::now() < deadline) @@ -1096,12 +1406,27 @@ namespace tut const std::string LIB = "disk:/s/lib.luau"; unit->start(doc, ALEditorCommand::FindReferences, refsOf("twice"), true, LIB, span(1, 11, 5), { place("", 1, 10) }, doc.editor->document().version()); - ensure("nothing read here", studio.expands.empty()); - ensure("held open for the walk", doc.lookup->pending == 1 && studio.shows == 0); - ensure("what it read handed back", pumped([&] { return !studio.expands.empty(); }, std::chrono::seconds(5))); - ensure("the one naming it, as it is on disk", studio.expands.size() == 1 && studio.expands[0].request.sourceText() == B_LUA); + // The thread the walks run on, made with the first of them, is the + // process's from here, as the viewer's is until it quits. + ll_test::StateGuard::accept("the live thread pools"); + ll_test::StateGuard::accept("the live work queues"); + UNSCOPED_INFO("nothing read here"); + CHECK(studio.expands.empty()); + { + INFO("held open for the walk"); + CHECK(doc.lookup->pending == 1); + CHECK(studio.shows == 0); + } + UNSCOPED_INFO("what it read handed back"); + REQUIRE(pumped([&] { return !studio.expands.empty(); }, std::chrono::seconds(5))); + { + INFO("the one naming it, as it is on disk"); + REQUIRE(studio.expands.size() == 1); + CHECK(studio.expands[0].request.sourceText() == B_LUA); + } studio.expands[0].expanded(expansion("b.luau", studio.expands[0].request.path, B_LUA)); - ensure_equals("shown once it is done with", studio.shows, 1); + UNSCOPED_INFO("shown once it is done with"); + CHECK(studio.shows == 1); // Begun again while its walk is under way: the walk let go of, and // only the new lookup's read looked through. @@ -1109,37 +1434,56 @@ namespace tut doc.editor->document().version()); unit->start(doc, ALEditorCommand::FindReferences, refsOf("twice"), true, LIB, span(1, 11, 5), { place("", 1, 10) }, doc.editor->document().version()); - ensure("the new one's handed back", pumped([&] { return studio.expands.size() > 1; }, std::chrono::seconds(5))); + UNSCOPED_INFO("the new one's handed back"); + REQUIRE(pumped([&] { return studio.expands.size() > 1; }, std::chrono::seconds(5))); pumped([] { return false; }, std::chrono::milliseconds(300)); - ensure_equals("and nothing of the old one's", studio.expands.size(), size_t(2)); + UNSCOPED_INFO("and nothing of the old one's"); + REQUIRE(studio.expands.size() == size_t(2)); studio.expands[1].expanded(expansion("b.luau", studio.expands[1].request.path, B_LUA)); - ensure_equals("shown once", studio.shows, 2); + UNSCOPED_INFO("shown once"); + CHECK(studio.shows == 2); // Begun again, and the lookups gone before what it read is back. unit->start(doc, ALEditorCommand::FindReferences, refsOf("twice"), true, LIB, span(1, 11, 5), { place("", 1, 10) }, doc.editor->document().version()); unit.reset(); pumped([] { return false; }, std::chrono::milliseconds(300)); - ensure_equals("nothing comes of it", studio.expands.size(), size_t(2)); + UNSCOPED_INFO("nothing comes of it"); + CHECK(studio.expands.size() == size_t(2)); std::error_code ec; fs::remove_all(root, ec); } - template<> template<> - void alscriptlookup_object::test<21>() + TEST_CASE_METHOD(alscriptlookup_data, "alscriptlookup: held lines match a tab's with a lone CR a break and CRLF one", "[alscriptlookup]") { - set_test_name("a text's lines held apart are the lines its tab would have: a lone CR a break, CRLF one"); const std::string text = "one\rtwo\r\nthree\n\rfive"; const ALScriptPlaces::Lines held(std::make_shared(text)); const ALTextDocument open(text); const ALScriptPlaces::Lines tab(&open); - ensure("as many as the tab's", held.has(4) && !held.has(5) && tab.has(4) && !tab.has(5)); + { + INFO("as many as the tab's"); + CHECK(held.has(4)); + CHECK_FALSE(held.has(5)); + CHECK(tab.has(4)); + CHECK_FALSE(tab.has(5)); + } for (S32 line = 0; line < 5; ++line) { - ensure_equals("line " + std::to_string(line) + " the tab's", held.line(line), tab.line(line)); + CAPTURE(line); + REQUIRE(held.line(line) == tab.line(line)); + } + { + INFO("each as it is"); + CHECK(held.line(1) == "two"); + CHECK(held.line(3).empty()); + CHECK(held.line(4) == "five"); } - ensure("each as it is", held.line(1) == "two" && held.line(3).empty() && held.line(4) == "five"); const ALScriptPlaces::Lines here(text); - ensure("read where it is, the same", here.line(2) == "three" && here.has(4) && !here.has(5)); + { + INFO("read where it is, the same"); + CHECK(here.line(2) == "three"); + CHECK(here.has(4)); + CHECK_FALSE(here.has(5)); + } } } diff --git a/indra/newview/tests/alscriptnavigation_test.cpp b/indra/newview/tests/alscriptnavigation_test.cpp index 0e799362a8..b5688d568c 100644 --- a/indra/newview/tests/alscriptnavigation_test.cpp +++ b/indra/newview/tests/alscriptnavigation_test.cpp @@ -30,7 +30,7 @@ #include "alscriptstudio_fixture.h" #include "llfocusmgr.h" -#include "../test/lltut.h" +#include #include #include @@ -79,7 +79,7 @@ namespace std::string lines(const std::optional& place) { return place ? place->doc + ":" + std::to_string(place->at.line) : "none"; } } -namespace tut +namespace { struct alscriptnavigation_data { @@ -93,7 +93,7 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } unit = std::make_unique(services, studio, studio); studio.navigation = unit.get(); @@ -130,23 +130,28 @@ namespace tut static void waitToSettle() { std::this_thread::sleep_for(std::chrono::milliseconds(400)); } }; - typedef test_group alscriptnavigation_group; - typedef alscriptnavigation_group::object alscriptnavigation_object; - alscriptnavigation_group alscriptnavigation_instance("alscriptnavigation"); - - template<> template<> - void alscriptnavigation_object::test<1>() + // A view of a line is a place of its own, and a place noted drops the + // way forward. + TEST_CASE_METHOD(alscriptnavigation_data, "alscriptnavigation: the history notes a line once and keeps the fifty latest places", "[alscriptnavigation]") { - set_test_name("the history: once a line, a view its own place, the fifty latest, and the way forward gone once a place is noted"); ALNavHistory history; const auto open = [](const std::string&) { return true; }; history.note(at("a", 1)); history.note(at("a", 1)); - ensure("once a line", lines(history.take(false, std::nullopt, open)) == "a:1" && !history.canGo(false)); + { + INFO("once a line"); + CHECK(lines(history.take(false, std::nullopt, open)) == "a:1"); + CHECK_FALSE(history.canGo(false)); + } history.note(at("a", 1)); history.note(at("a", 1, Doc::View::Expanded)); std::optional expanded = history.take(false, std::nullopt, open); - ensure("the expansion's line its own place", expanded && expanded->view == Doc::View::Expanded && history.canGo(false)); + { + INFO("the expansion's line its own place"); + REQUIRE(expanded); + CHECK(expanded->view == Doc::View::Expanded); + CHECK(history.canGo(false)); + } history.take(false, std::nullopt, open); for (S32 i = 0; i < 60; ++i) { @@ -159,18 +164,23 @@ namespace tut oldest = history.take(false, std::nullopt, open); ++count; } - ensure("fifty, the oldest dropped", count == 50 && lines(oldest) == "a:10"); + { + INFO("fifty, the oldest dropped"); + CHECK(count == 50); + CHECK(lines(oldest) == "a:10"); + } history.note(at("a", 1)); history.take(false, at("b", 2), open); - ensure("where it went from, forward", history.canGo(true)); + UNSCOPED_INFO("where it went from, forward"); + CHECK(history.canGo(true)); history.note(at("c", 3)); - ensure("a place noted: the way forward gone", !history.canGo(true)); + UNSCOPED_INFO("a place noted: the way forward gone"); + CHECK_FALSE(history.canGo(true)); } - template<> template<> - void alscriptnavigation_object::test<2>() + // Where it goes from is put on the other way. + TEST_CASE_METHOD(alscriptnavigation_data, "alscriptnavigation: back and forward pass closed tabs over and follow renamed ones", "[alscriptnavigation]") { - set_test_name("back and forward: where it goes from put on the other way, a closed tab passed over, a tab renamed followed"); ALNavHistory history; std::set closed; const auto open = [&closed](const std::string& id) { return closed.count(id) == 0; }; @@ -178,96 +188,134 @@ namespace tut history.note(at("gone", 2)); history.note(at("b", 3)); closed.insert("gone"); - ensure("back", lines(history.take(false, at("c", 4), open)) == "b:3"); - ensure("back past the closed one", lines(history.take(false, at("b", 3), open)) == "a:1" && !history.canGo(false)); - ensure("forward again", lines(history.take(true, at("a", 1), open)) == "b:3"); + UNSCOPED_INFO("back"); + CHECK(lines(history.take(false, at("c", 4), open)) == "b:3"); + { + INFO("back past the closed one"); + CHECK(lines(history.take(false, at("b", 3), open)) == "a:1"); + CHECK_FALSE(history.canGo(false)); + } + UNSCOPED_INFO("forward again"); + CHECK(lines(history.take(true, at("a", 1), open)) == "b:3"); history.rekey("c", "renamed"); - ensure("followed", lines(history.take(true, std::nullopt, open)) == "renamed:4"); - ensure("nowhere left", !history.take(true, std::nullopt, open)); + UNSCOPED_INFO("the renamed tab followed"); + CHECK(lines(history.take(true, std::nullopt, open)) == "renamed:4"); + UNSCOPED_INFO("nowhere left"); + CHECK_FALSE(history.take(true, std::nullopt, open)); } - template<> template<> - void alscriptnavigation_object::test<3>() + TEST_CASE_METHOD(alscriptnavigation_data, "alscriptnavigation: a walk is noted as one jump and back and forward are shown by the window", "[alscriptnavigation]") { - set_test_name("a jump from the tab in front, a walk one jump until it ends; back and forward shown by the window"); ALScriptNavigation& unit = make(); Doc& a = tab("a", 1); services.front = 0; unit.noteJump(true); a.editor->setCaret(ALTextPos(3, 0)); unit.noteJump(true); - ensure("a walk: one jump", unit.walking()); + UNSCOPED_INFO("a walk: one jump"); + CHECK(unit.walking()); unit.goBack(false); - ensure("back where the walk began", studio.shown == Names{ "a:1" } && !unit.walking() && !unit.canGo(false)); + { + INFO("back where the walk began"); + CHECK(studio.shown == Names{ "a:1" }); + CHECK_FALSE(unit.walking()); + CHECK_FALSE(unit.canGo(false)); + } unit.goBack(true); - ensure("and forward to where it was then", studio.shown == Names{ "a:1", "a:3" }); + UNSCOPED_INFO("and forward to where it was then"); + CHECK(studio.shown == Names{ "a:1", "a:3" }); unit.noteJump(true); unit.walked(); a.editor->setCaret(ALTextPos(2, 0)); unit.noteJump(true); unit.goBack(false); - ensure("a walk ended: the next is a jump of its own", studio.shown.back() == "a:2"); + UNSCOPED_INFO("a walk ended: the next is a jump of its own"); + CHECK(studio.shown.back() == "a:2"); a.loaded = false; unit.noteJump(); - ensure("a tab not loaded: nothing noted", !unit.canGo(false) || studio.shown.size() == 3); + UNSCOPED_INFO("a tab not loaded: nothing noted"); + CHECK((!unit.canGo(false) || studio.shown.size() == 3)); } - template<> template<> - void alscriptnavigation_object::test<4>() + TEST_CASE_METHOD(alscriptnavigation_data, "alscriptnavigation: a row stayed on opens as a preview unless already open or passed or left", "[alscriptnavigation]") { - set_test_name("a row stayed on a moment opened as a preview; one open already, walked past, or left, not"); ALScriptNavigation& unit = make(); ALPaneList* l = list(); studio.open.insert("open path"); - ensure("open already: at once", !unit.deferOpen(l, "open path")); - ensure("nowhere: at once", !unit.deferOpen(l, "")); - ensure("put off", unit.deferOpen(l, "a path")); + UNSCOPED_INFO("open already: at once"); + CHECK_FALSE(unit.deferOpen(l, "open path")); + UNSCOPED_INFO("nowhere: at once"); + CHECK_FALSE(unit.deferOpen(l, "")); + UNSCOPED_INFO("put off"); + CHECK(unit.deferOpen(l, "a path")); unit.pumpSettle(); - ensure("not yet", studio.chosen == 0); + UNSCOPED_INFO("not yet"); + CHECK(studio.chosen == 0); waitToSettle(); unit.pumpSettle(); - ensure("stayed on: opened, as a preview", studio.chosen == 1 && studio.previewing && !unit.openingPreview()); + { + INFO("stayed on: opened, as a preview"); + CHECK(studio.chosen == 1); + CHECK(studio.previewing); + CHECK_FALSE(unit.openingPreview()); + } unit.pumpSettle(); - ensure("once", studio.chosen == 1); + UNSCOPED_INFO("once"); + CHECK(studio.chosen == 1); unit.deferOpen(l, "a path"); l->selectByValue(LLSD("b")); waitToSettle(); unit.pumpSettle(); - ensure("walked past: not", studio.chosen == 1); + UNSCOPED_INFO("walked past: not"); + CHECK(studio.chosen == 1); unit.deferOpen(l, "a path"); gFocusMgr.setKeyboardFocus(nullptr); waitToSettle(); unit.pumpSettle(); - ensure("left: not", studio.chosen == 1); + UNSCOPED_INFO("left: not"); + CHECK(studio.chosen == 1); } - template<> template<> - void alscriptnavigation_object::test<5>() + TEST_CASE_METHOD(alscriptnavigation_data, "alscriptnavigation: the preview is let go of unless typed in or worked from or revealed", "[alscriptnavigation]") { - set_test_name("the preview let go of for the next, held where anything was done in it or it is worked from; to the editor, held"); ALScriptNavigation& unit = make(); Doc& a = tab("a"); a.preview = true; unit.closePreview(); - ensure("let go of", studio.letGo == Names{ "a" }); + UNSCOPED_INFO("let go of"); + CHECK(studio.letGo == Names{ "a" }); Doc& b = tab("b"); b.preview = true; b.editor->insertText("typed"); unit.closePreview(); - ensure("typed in: held", !b.preview && studio.tabs == 1 && studio.letGo.size() == 1); + { + INFO("typed in: held"); + CHECK_FALSE(b.preview); + CHECK(studio.tabs == 1); + CHECK(studio.letGo.size() == 1); + } Doc& c = tab("c"); c.preview = true; studio.worked = "c"; unit.closePreview(); - ensure("worked from: held", !c.preview && studio.letGo.size() == 1); + { + INFO("worked from: held"); + CHECK_FALSE(c.preview); + CHECK(studio.letGo.size() == 1); + } Doc& d = tab("d"); d.preview = true; services.front = 3; unit.revealed(nullptr, true); - ensure("taken to the editor: held, and given the keyboard", !d.preview && studio.focused == Names{ "d" }); + { + INFO("taken to the editor: held, and given the keyboard"); + CHECK_FALSE(d.preview); + CHECK(studio.focused == Names{ "d" }); + } ALPaneList* l = list(); gFocusMgr.setKeyboardFocus(nullptr); unit.revealed(l, false); - ensure("back to the list", l->hasFocus()); + UNSCOPED_INFO("back to the list"); + CHECK(l->hasFocus()); } } diff --git a/indra/newview/tests/alscriptobjectcheck_test.cpp b/indra/newview/tests/alscriptobjectcheck_test.cpp index 3a6ebb765c..b8547080a5 100644 --- a/indra/newview/tests/alscriptobjectcheck_test.cpp +++ b/indra/newview/tests/alscriptobjectcheck_test.cpp @@ -34,7 +34,7 @@ #include "alscriptstudio_fixture.h" -#include "../test/lltut.h" +#include #include #include @@ -144,7 +144,7 @@ namespace } } -namespace tut +namespace { struct alscriptobjectcheck_data { @@ -166,54 +166,83 @@ namespace tut window.texts = { { id(11), "default {}\n" }, { id(12), "default {}\n" } }; } }; - typedef test_group alscriptobjectcheck_group; - typedef alscriptobjectcheck_group::object alscriptobjectcheck_object; - alscriptobjectcheck_group alscriptobjectcheck_instance("alscriptobjectcheck"); - template<> template<> - void alscriptobjectcheck_object::test<1>() + TEST_CASE_METHOD(alscriptobjectcheck_data, "alscriptobjectcheck: a check reads scripts two at a time and reports open and unread ones", "[alscriptobjectcheck]") { - set_test_name("an object's scripts checked a few at a time, the open ones left to their tabs; one not read and the prims that did not answer said when done"); house(); check.check(root, "House"); - ensure("listed", window.listedFor == std::vector{ root }); - ensure("running", check.running()); - ensure_equals("two at once", window.asks.size(), size_t(2)); - ensure("the open one not read", window.reads.size() == 2 && window.reads[0].item == id(11) && window.reads[1].item == id(12)); - ensure("a check of what was read", window.asks[0].request.kind == ALScriptAnalysis::Kind::Check && *window.asks[0].request.text == "default {}\n"); - ensure("for its problems alone", !window.asks[0].request.semantics && !window.asks[0].request.hintTypes); - ensure("not expanded, the preprocessor off", window.expands.empty()); + CHECK(window.listedFor == std::vector{ root }); + CHECK(check.running()); + UNSCOPED_INFO("two at once"); + REQUIRE(window.asks.size() == size_t(2)); + { + INFO("the open one not read"); + REQUIRE(window.reads.size() == 2); + CHECK(window.reads[0].item == id(11)); + CHECK(window.reads[1].item == id(12)); + } + { + INFO("a check of what was read"); + REQUIRE(window.asks[0].request.kind == ALScriptAnalysis::Kind::Check); + CHECK(*window.asks[0].request.text == "default {}\n"); + } + { + INFO("for its problems alone"); + CHECK_FALSE(window.asks[0].request.semantics); + CHECK_FALSE(window.asks[0].request.hintTypes); + } + UNSCOPED_INFO("not expanded, the preprocessor off"); + REQUIRE(window.expands.empty()); window.asks[0].answered(answer({ problem(0, "wrong"), problem(1, "unwanted", ALScriptProblem::Severity::Warning, "off") })); - ensure_equals("B checked", window.checked.size(), size_t(1)); + UNSCOPED_INFO("B checked"); + REQUIRE(window.checked.size() == size_t(1)); const Check::Script& b = window.checked[0]; - ensure("by its name", b.name == "B" && b.ref.item == id(11)); - ensure_equals("the lints as chosen applied", b.rows.size(), size_t(1)); - ensure("said as a tab's row", b.rows[0].message == "wrong" && b.rows[0].level == ALScriptStudioDoc::Level::Error && b.rows[0].line == 0); - ensure("the next begun, which could not be read", window.reads.size() == 3 && window.reads[2].item == id(13)); - ensure("not done while C is out", window.done.empty()); + CHECK(b.name == "B"); + CHECK(b.ref.item == id(11)); + UNSCOPED_INFO("the lints as chosen applied"); + REQUIRE(b.rows.size() == size_t(1)); + { + INFO("said as a tab's row"); + CHECK(b.rows[0].message == "wrong"); + CHECK(b.rows[0].level == ALScriptStudioDoc::Level::Error); + CHECK(b.rows[0].line == 0); + } + { + INFO("the next begun, which could not be read"); + REQUIRE(window.reads.size() == 3); + CHECK(window.reads[2].item == id(13)); + } + UNSCOPED_INFO("not done while C is out"); + REQUIRE(window.done.empty()); window.asks[1].answered(answer({})); - ensure_equals("done", window.done.size(), size_t(1)); + REQUIRE(window.done.size() == size_t(1)); const Check::Done& done = window.done[0]; - ensure("of the house", done.root == root && done.name == "House" && done.present); - ensure_equals("checked", done.checked, 2); - ensure_equals("left to its tab", done.open, 1); - ensure_equals("prims that did not answer", done.unlisted, 1); - ensure("the one not read, by name", done.unread == std::vector{ "D" }); - ensure("no longer running", !check.running()); + CHECK(done.root == root); + CHECK(done.name == "House"); + CHECK(done.present); + CHECK(done.checked == 2); + UNSCOPED_INFO("left to its tab"); + CHECK(done.open == 1); + UNSCOPED_INFO("prims that did not answer"); + CHECK(done.unlisted == 1); + CHECK(done.unread == std::vector{ "D" }); + CHECK_FALSE(check.running()); } - template<> template<> - void alscriptobjectcheck_object::test<2>() + TEST_CASE_METHOD(alscriptobjectcheck_data, "alscriptobjectcheck: a preprocessed script's problems map back to it or its include", "[alscriptobjectcheck]") { - set_test_name("with the preprocessor on, each script expanded first and its problems read back through the expansion to the script or its include"); window.preprocess = true; window.listing.scripts.push_back({ ALScriptRef(root, id(20)), "Main" }); window.texts = { { id(20), "#include \"lib\"\ndefault {}\n" } }; check.check(root, "Shed"); - ensure_equals("expanded", window.expands.size(), size_t(1)); - ensure("apart, as a tab's check", window.expands[0].request.apart && window.expands[0].request.sourceText() == "#include \"lib\"\ndefault {}\n"); + REQUIRE(window.expands.size() == size_t(1)); + { + INFO("apart, as a tab's check"); + CHECK(window.expands[0].request.apart); + CHECK(window.expands[0].request.sourceText() == "#include \"lib\"\ndefault {}\n"); + } // The include's line, then the script's own. ALPreprocessor::Result result; @@ -239,23 +268,36 @@ namespace tut said.message = "from the preprocessor"; result.problems.push_back(said); window.expands[0].expanded(result); - ensure_equals("asked", window.asks.size(), size_t(1)); - ensure_equals("about the expansion", *window.asks[0].request.text, std::string("integer x;\ndefault {}\n")); + REQUIRE(window.asks.size() == size_t(1)); + UNSCOPED_INFO("about the expansion"); + CHECK(*window.asks[0].request.text == std::string("integer x;\ndefault {}\n")); window.asks[0].answered(answer({ problem(0, "in the library"), problem(1, "in the script") })); - ensure_equals("checked", window.checked.size(), size_t(1)); + REQUIRE(window.checked.size() == size_t(1)); const std::vector& rows = window.checked[0].rows; - ensure_equals("the preprocessor's and the analyzers'", rows.size(), size_t(3)); - ensure("the preprocessor's first", rows[0].message == "from the preprocessor" && rows[0].origin == "OriginPreprocessor"); - ensure("the include's, under its name", rows[1].file == "disk:/lib.lsl" && rows[1].fileName == "lib" && rows[1].line == 0); - ensure("the script's own, at its line", rows[2].file.empty() && rows[2].line == 1); - ensure_equals("done", window.done.size(), size_t(1)); + UNSCOPED_INFO("the preprocessor's and the analyzers'"); + REQUIRE(rows.size() == size_t(3)); + { + INFO("the preprocessor's first"); + CHECK(rows[0].message == "from the preprocessor"); + CHECK(rows[0].origin == "OriginPreprocessor"); + } + { + INFO("the include's, under its name"); + CHECK(rows[1].file == "disk:/lib.lsl"); + CHECK(rows[1].fileName == "lib"); + CHECK(rows[1].line == 0); + } + { + INFO("the script's own, at its line"); + REQUIRE(rows[2].file.empty()); + CHECK(rows[2].line == 1); + } + REQUIRE(window.done.size() == size_t(1)); } - template<> template<> - void alscriptobjectcheck_object::test<3>() + TEST_CASE_METHOD(alscriptobjectcheck_data, "alscriptobjectcheck: a restarted check drops stale answers and an absent object ends at once", "[alscriptobjectcheck]") { - set_test_name("a check begun again lets the last one's answers go; an object out of sight is said so at once"); house(); check.check(root, "House"); std::function stale = std::move(window.asks[0].answered); @@ -263,13 +305,20 @@ namespace tut window.checked.clear(); check.check(root, "House"); stale(answer({ problem(0, "old") })); - ensure("the earlier answer dropped", window.checked.empty()); - ensure_equals("the new one's asked", window.asks.size(), size_t(2)); + UNSCOPED_INFO("the earlier answer dropped"); + REQUIRE(window.checked.empty()); + UNSCOPED_INFO("the new one's asked"); + REQUIRE(window.asks.size() == size_t(2)); FakeWindow away; away.listing.present = false; Check gone(services, away, away); gone.check(id(99), "Gone"); - ensure("said at once, as not in sight", away.done.size() == 1 && !away.done[0].present && away.done[0].checked == 0); + { + INFO("said at once, as not in sight"); + REQUIRE(away.done.size() == 1); + CHECK_FALSE(away.done[0].present); + CHECK(away.done[0].checked == 0); + } } } diff --git a/indra/newview/tests/alscriptoutlinepane_test.cpp b/indra/newview/tests/alscriptoutlinepane_test.cpp index 6acb2b0fe9..ea2467aa97 100644 --- a/indra/newview/tests/alscriptoutlinepane_test.cpp +++ b/indra/newview/tests/alscriptoutlinepane_test.cpp @@ -38,7 +38,7 @@ #include "llscrolllistitem.h" #include "lltextbox.h" -#include "../test/lltut.h" +#include #include @@ -104,7 +104,7 @@ namespace } } -namespace tut +namespace { struct alscriptoutlinepane_data { @@ -116,7 +116,7 @@ namespace tut ALScriptOutlinePane* found = window.find("outline_pane"); if (!found) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } return found; } @@ -159,171 +159,211 @@ namespace tut std::string comment() { return pane()->list()->saysEmpty() ? pane()->list()->emptyWords() : std::string(); } }; - typedef test_group alscriptoutlinepane_group; - typedef alscriptoutlinepane_group::object alscriptoutlinepane_object; - alscriptoutlinepane_group alscriptoutlinepane_instance("alscriptoutlinepane"); - - template<> template<> - void alscriptoutlinepane_object::test<1>() + TEST_CASE_METHOD(alscriptoutlinepane_data, "alscriptoutlinepane: only the front tab's symbols show as a tree and the window is told", "[alscriptoutlinepane]") { - set_test_name("the tab in front's symbols as a tree, marked and explained, the window told; another tab's, or none, not"); ALScriptOutlinePane* outline = pane(); Doc& doc = tab("a"); Doc& other = tab("b"); outline->show(other); - ensure("not in front: nothing", outline->list()->getItemCount() == 0 && told().shown.empty()); + { + INFO("not in front: nothing"); + CHECK(outline->list()->getItemCount() == 0); + CHECK(told().shown.empty()); + } outline->show(doc); - ensure_equals("the tree", rows(), std::string("count, ^default, .touch_start, .state_entry, alpha")); - ensure_equals("told", joined(told().shown), std::string("a")); + REQUIRE(rows() == std::string("count, ^default, .touch_start, .state_entry, alpha")); + CHECK(joined(told().shown) == std::string("a")); const std::vector items = outline->list()->getAllData(); - ensure_equals("what it is", items[1]->getColumn(0)->getToolTip(), std::string("state")); - ensure_equals("and its declaration", items[0]->getColumn(1)->getToolTip(), std::string("variable\ninteger")); - ensure_equals("by its index", items[4]->getValue().asInteger(), 4); - ensure("nothing said over it", comment().empty()); + UNSCOPED_INFO("what it is"); + CHECK(items[1]->getColumn(0)->getToolTip() == std::string("state")); + UNSCOPED_INFO("and its declaration"); + CHECK(items[0]->getColumn(1)->getToolTip() == std::string("variable\ninteger")); + UNSCOPED_INFO("by its index"); + CHECK(items[4]->getValue().asInteger() == 4); + CHECK(comment().empty()); doc.outline.clear(); outline->show(doc); - ensure_equals("nothing to outline", comment(), std::string("Nothing in this script to outline yet.")); + CHECK(comment() == std::string("Nothing in this script to outline yet.")); doc.loaded = false; outline->show(doc); - ensure_equals("not loaded yet", comment(), std::string("Still loading.")); + CHECK(comment() == std::string("Still loading.")); } - template<> template<> - void alscriptoutlinepane_object::test<2>() + TEST_CASE_METHOD(alscriptoutlinepane_data, "alscriptoutlinepane: folds are kept by name through renumbering and left goes to the holder", "[alscriptoutlinepane]") { - set_test_name("folded shut and opened by the names down to it, which a check that numbers them anew keeps; left from a leaf to its holder"); ALScriptOutlinePane* outline = pane(); Doc& doc = tab("a"); outline->show(doc); outline->fold(1); - ensure_equals("shut", rows(), std::string("count, >default, alpha")); - ensure("kept by name", doc.outlineFolded.contains("default")); - ensure_equals("the row it was", chosen(), 1); + UNSCOPED_INFO("shut"); + CHECK(rows() == std::string("count, >default, alpha")); + UNSCOPED_INFO("kept by name"); + CHECK(doc.outlineFolded.contains("default")); + UNSCOPED_INFO("the row it was"); + CHECK(chosen() == 1); outline->fold(1); - ensure_equals("open", rows(), std::string("count, ^default, .touch_start, .state_entry, alpha")); + UNSCOPED_INFO("open"); + CHECK(rows() == std::string("count, ^default, .touch_start, .state_entry, alpha")); outline->fold(1, false); - ensure("already open: left so", !doc.outlineFolded.contains("default")); + UNSCOPED_INFO("already open: left so"); + CHECK_FALSE(doc.outlineFolded.contains("default")); outline->fold(1, true); doc.outline.insert(doc.outline.begin(), entry("before", ALScriptSymbolKind::Variable, 0, 0)); outline->show(doc); - ensure_equals("numbered anew, still shut", rows(), std::string("before, count, >default, alpha")); + UNSCOPED_INFO("numbered anew, still shut"); + CHECK(rows() == std::string("before, count, >default, alpha")); outline->fold(2); outline->fold(4, true); - ensure_equals("from a leaf to what holds it", chosen(), 2); - ensure_equals("gone to as a row walked to is", joined(told().chosen), std::string("default")); - ensure("the list kept", !window.services().reveals.empty() && !window.services().reveals.back()); + UNSCOPED_INFO("from a leaf to what holds it"); + CHECK(chosen() == 2); + UNSCOPED_INFO("gone to as a row walked to is"); + CHECK(joined(told().chosen) == std::string("default")); + { + INFO("the list kept"); + REQUIRE_FALSE(window.services().reveals.empty()); + CHECK_FALSE(window.services().reveals.back()); + } outline->fold(0, true); - ensure_equals("a leaf at the top: nowhere", joined(told().chosen), std::string("default")); + UNSCOPED_INFO("a leaf at the top: nowhere"); + CHECK(joined(told().chosen) == std::string("default")); outline->fold(4, false); - ensure_equals("right on a leaf: nothing", joined(told().chosen), std::string("default")); + UNSCOPED_INFO("right on a leaf: nothing"); + CHECK(joined(told().chosen) == std::string("default")); } - template<> template<> - void alscriptoutlinepane_object::test<3>() + TEST_CASE_METHOD(alscriptoutlinepane_data, "alscriptoutlinepane: the filter flattens the tree and the sort by name or kind is kept", "[alscriptoutlinepane]") { - set_test_name("through the filter, flat and unfoldable; sorted by name or kind under each holder, the window told"); ALScriptOutlinePane* outline = pane(); Doc& doc = tab("a"); LLFilterEditor* filter = window.find("outline_filter"); filter->setText(std::string("ST")); filter->onCommit(); - ensure_equals("each with the letters", rows(), std::string("touch_start, state_entry")); + UNSCOPED_INFO("each with the letters"); + CHECK(rows() == std::string("touch_start, state_entry")); filter->setText(std::string("zz")); outline->show(doc); - ensure_equals("none", comment(), std::string("No symbol names match what you typed.")); + CHECK(comment() == std::string("No symbol names match what you typed.")); filter->setText(std::string()); outline->setSortOrder("name"); outline->show(doc); - ensure_equals("by name", rows(), std::string("alpha, count, ^default, .state_entry, .touch_start")); - ensure_equals("kept", outline->sortOrder(), std::string("name")); + UNSCOPED_INFO("by name"); + CHECK(rows() == std::string("alpha, count, ^default, .state_entry, .touch_start")); + UNSCOPED_INFO("kept"); + CHECK(outline->sortOrder() == std::string("name")); LLComboBox* sort = window.find("outline_sort"); sort->selectByValue(LLSD("kind")); sort->onCommit(); - ensure_equals("by kind, then name", rows(), std::string("count, alpha, ^default, .state_entry, .touch_start")); - ensure_equals("the window told", told().sorts, 1); + UNSCOPED_INFO("by kind, then name"); + CHECK(rows() == std::string("count, alpha, ^default, .state_entry, .touch_start")); + CHECK(told().sorts == 1); filter->setText(std::string("t")); outline->show(doc); - ensure_equals("sorted through the filter too", rows(), std::string("count, state_entry, touch_start, default")); + UNSCOPED_INFO("sorted through the filter too"); + CHECK(rows() == std::string("count, state_entry, touch_start, default")); } - template<> template<> - void alscriptoutlinepane_object::test<4>() + TEST_CASE_METHOD(alscriptoutlinepane_data, "alscriptoutlinepane: the caret's symbol or its nearest shown holder is chosen", "[alscriptoutlinepane]") { - set_test_name("the symbol the caret is in chosen, or the nearest shown that holds it; none where it is in none"); ALScriptOutlinePane* outline = pane(); Doc& doc = tab("a"); outline->show(doc); doc.caret->crumbPath = { 1, 3 }; outline->followCaret(doc); - ensure_equals("the innermost", chosen(), 3); + UNSCOPED_INFO("the innermost"); + CHECK(chosen() == 3); outline->fold(1); outline->list()->deselectAllItems(true); doc.caret->crumbPath = { 1, 3 }; outline->followCaret(doc); - ensure_equals("folded away: its holder", chosen(), 1); + UNSCOPED_INFO("folded away: its holder"); + CHECK(chosen() == 1); doc.caret->crumbPath.clear(); outline->followCaret(doc); - ensure_equals("in none", chosen(), -1); + UNSCOPED_INFO("in none"); + CHECK(chosen() == -1); outline->list()->selectByValue(LLSD(0)); doc.caret->crumbPath = { 9 }; outline->followCaret(doc); - ensure_equals("not listed", chosen(), -1); + UNSCOPED_INFO("not listed"); + CHECK(chosen() == -1); outline->fold(1); doc.caret->crumbPath = { 1, 2 }; doc.outline[0].detail = "float"; outline->show(doc); - ensure_equals("followed as it is shown", chosen(), 2); - ensure("going nowhere", told().chosen.empty()); + UNSCOPED_INFO("followed as it is shown"); + CHECK(chosen() == 2); + UNSCOPED_INFO("going nowhere"); + CHECK(told().chosen.empty()); } - template<> template<> - void alscriptoutlinepane_object::test<5>() + TEST_CASE_METHOD(alscriptoutlinepane_data, "alscriptoutlinepane: the list is made again only when what it shows changes", "[alscriptoutlinepane]") { - set_test_name("a check that changes nothing shown leaves the list; a change makes it again; no tab, and it is made again whatever"); ALScriptOutlinePane* outline = pane(); Doc& doc = tab("a"); outline->show(doc); // A mark left on the first row, which a list made again does not have. outline->list()->getFirstData()->getColumn(1)->setValue(LLSD("marked")); outline->show(doc); - ensure_equals("the same rows", cell(outline->list()->getFirstData(), 1), std::string("marked")); + UNSCOPED_INFO("the same rows"); + CHECK(cell(outline->list()->getFirstData(), 1) == std::string("marked")); doc.outline[0].detail = "float"; outline->show(doc); - ensure("a detail changed: made again", cell(outline->list()->getFirstData(), 1) != "marked"); + UNSCOPED_INFO("a detail changed: made again"); + CHECK(cell(outline->list()->getFirstData(), 1) != "marked"); outline->list()->getFirstData()->getColumn(1)->setValue(LLSD("marked")); Doc& other = tab("b"); other.outline[0].detail = "float"; window.services().front = 1; outline->show(other); - ensure("another tab's, the same rows: made again", cell(outline->list()->getFirstData(), 1) != "marked"); + UNSCOPED_INFO("another tab's, the same rows: made again"); + CHECK(cell(outline->list()->getFirstData(), 1) != "marked"); window.services().front = 0; outline->forget(); - ensure("nothing listed", outline->list()->getItemCount() == 0); + CHECK(outline->list()->getItemCount() == 0); outline->show(doc); - ensure_equals("the same rows back", outline->list()->getItemCount(), 5); + UNSCOPED_INFO("the same rows back"); + CHECK(outline->list()->getItemCount() == 5); } - template<> template<> - void alscriptoutlinepane_object::test<6>() + TEST_CASE_METHOD(alscriptoutlinepane_data, "alscriptoutlinepane: choosing a row goes there and keys and arrow clicks fold", "[alscriptoutlinepane]") { - set_test_name("a row chosen gone to; return to type there, escape back without going; left and right fold; an arrow clicked folds"); ALScriptOutlinePane* outline = pane(); Doc& doc = tab("a"); outline->show(doc); ALPaneList* list = outline->list(); list->selectByValue(LLSD(0)); list->onCommit(); - ensure("gone to", joined(told().chosen) == "count" && window.services().reveals.back() == false); - ensure("return", list->handleKeyHere(KEY_RETURN, MASK_NONE) && told().chosen.back() == "count editor"); - ensure("the keyboard taken", window.services().reveals.back()); - ensure("escape", list->handleKeyHere(KEY_ESCAPE, MASK_NONE) && told().chosen.size() == 2 && window.services().reveals.size() == 3); - ensure("with a key held: the list's", !list->handleKeyHere(KEY_RETURN, MASK_CONTROL) && told().chosen.size() == 2); + { + INFO("gone to"); + CHECK(joined(told().chosen) == "count"); + CHECK_FALSE(window.services().reveals.back()); + } + { + INFO("return: gone to, to type there"); + CHECK(list->handleKeyHere(KEY_RETURN, MASK_NONE)); + CHECK(told().chosen.back() == "count editor"); + } + UNSCOPED_INFO("the keyboard taken"); + CHECK(window.services().reveals.back()); + { + INFO("escape: back without going"); + CHECK(list->handleKeyHere(KEY_ESCAPE, MASK_NONE)); + CHECK(told().chosen.size() == 2); + CHECK(window.services().reveals.size() == 3); + } + { + INFO("with a key held: the list's"); + CHECK_FALSE(list->handleKeyHere(KEY_RETURN, MASK_CONTROL)); + CHECK(told().chosen.size() == 2); + } list->selectByValue(LLSD(1)); - ensure("left", list->handleKeyHere(KEY_LEFT, MASK_NONE) && doc.outlineFolded.contains("default")); - ensure("right", list->handleKeyHere(KEY_RIGHT, MASK_NONE) && !doc.outlineFolded.contains("default")); + CHECK(list->handleKeyHere(KEY_LEFT, MASK_NONE)); + CHECK(doc.outlineFolded.contains("default")); + CHECK(list->handleKeyHere(KEY_RIGHT, MASK_NONE)); + CHECK_FALSE(doc.outlineFolded.contains("default")); // The mouse only reaches what is shown. window.floater->setVisible(true); @@ -341,78 +381,97 @@ namespace tut --row_y; } row_y -= 2; - ensure_equals("row 1 found", row(row_y), 1); + UNSCOPED_INFO("row 1 found"); + CHECK(row(row_y) == 1); list->localPointToOtherView(rect.mLeft + icon->getWidth() + list->getColumnPadding() + 2, row_y, &x, &y, outline); - ensure("an arrow clicked", outline->handleMouseDown(x, y, MASK_NONE)); - ensure("folds", doc.outlineFolded.contains("default")); + UNSCOPED_INFO("an arrow clicked"); + CHECK(outline->handleMouseDown(x, y, MASK_NONE)); + CHECK(doc.outlineFolded.contains("default")); list->localPointToOtherView(rect.mRight - 4, row_y, &x, &y, outline); outline->handleMouseDown(x, y, MASK_NONE); - ensure("past it: not", doc.outlineFolded.contains("default")); + UNSCOPED_INFO("past it: not"); + CHECK(doc.outlineFolded.contains("default")); // Row 0, a leaf, where an arrow would be: the list's, which chooses it. list->deselectAllItems(true); const S32 leaf_y = row_y + (rect.mTop - row_y) / 2 + 1; list->localPointToOtherView(rect.mLeft + icon->getWidth() + list->getColumnPadding() + 2, leaf_y, &x, &y, outline); - ensure_equals("a leaf row there", row(leaf_y), 0); + UNSCOPED_INFO("a leaf row there"); + CHECK(row(leaf_y) == 0); outline->handleMouseDown(x, y, MASK_NONE); - ensure_equals("chosen by the list", chosen(), 0); + UNSCOPED_INFO("chosen by the list"); + CHECK(chosen() == 0); } - template<> template<> - void alscriptoutlinepane_object::test<7>() + TEST_CASE_METHOD(alscriptoutlinepane_data, "alscriptoutlinepane: out of sight it only tells the window and lists once seen", "[alscriptoutlinepane]") { - set_test_name("out of sight, only the window told; listed, and the caret followed, once it is seen"); ALScriptOutlinePane* outline = pane(); Doc& doc = tab("a"); window.floater->setVisible(false); outline->show(doc); - ensure("told", joined(told().shown) == "a"); - ensure("not listed", outline->list()->getItemCount() == 0); + CHECK(joined(told().shown) == "a"); + CHECK(outline->list()->getItemCount() == 0); outline->pump(); - ensure("nor while unseen", outline->list()->getItemCount() == 0); + UNSCOPED_INFO("nor while unseen"); + CHECK(outline->list()->getItemCount() == 0); window.floater->setVisible(true); outline->pump(); - ensure_equals("seen: listed", rows(), std::string("count, ^default, .touch_start, .state_entry, alpha")); + UNSCOPED_INFO("seen: listed"); + CHECK(rows() == std::string("count, ^default, .touch_start, .state_entry, alpha")); window.floater->setVisible(false); doc.caret->crumbPath = { 1, 3 }; outline->followCaret(doc); - ensure_equals("not followed unseen", chosen(), -1); + UNSCOPED_INFO("not followed unseen"); + CHECK(chosen() == -1); window.floater->setVisible(true); outline->pump(); - ensure_equals("followed once seen", chosen(), 3); + UNSCOPED_INFO("followed once seen"); + CHECK(chosen() == 3); outline->pump(); - ensure_equals("and then left be", joined(told().shown), std::string("a, a, a")); + UNSCOPED_INFO("and then left be"); + CHECK(joined(told().shown) == std::string("a, a, a")); } - template<> template<> - void alscriptoutlinepane_object::test<8>() + TEST_CASE_METHOD(alscriptoutlinepane_data, "alscriptoutlinepane: a symbol added keeps the other rows and the choice", "[alscriptoutlinepane]") { - set_test_name("a symbol added among the others: its row made, the others the same rows where they were, the row chosen kept"); ALScriptOutlinePane* outline = pane(); Doc& doc = tab("a"); outline->show(doc); const std::vector before = outline->list()->getAllData(); doc.caret->crumbPath = { 4 }; outline->followCaret(doc); - ensure_equals("alpha chosen", chosen(), 4); + UNSCOPED_INFO("alpha chosen"); + CHECK(chosen() == 4); doc.outline.insert(doc.outline.begin() + 1, entry("beta", ALScriptSymbolKind::Function, 0, 1)); doc.caret->crumbPath = { 5 }; outline->show(doc); - ensure_equals("in its place", rows(), std::string("count, beta, ^default, .touch_start, .state_entry, alpha")); + UNSCOPED_INFO("in its place"); + CHECK(rows() == std::string("count, beta, ^default, .touch_start, .state_entry, alpha")); const std::vector after = outline->list()->getAllData(); - ensure("the others the same rows", after.size() == 6 && after[0] == before[0] && after[2] == before[1] && after[5] == before[4]); - ensure_equals("alpha still chosen, by its new place", chosen(), 5); + { + INFO("the others the same rows"); + REQUIRE(after.size() == 6); + CHECK(after[0] == before[0]); + CHECK(after[2] == before[1]); + CHECK(after[5] == before[4]); + } + UNSCOPED_INFO("alpha still chosen, by its new place"); + CHECK(chosen() == 5); } - template<> template<> - void alscriptoutlinepane_object::test<9>() + TEST_CASE_METHOD(alscriptoutlinepane_data, "alscriptoutlinepane: the heading fits its word with the sort and filter after it", "[alscriptoutlinepane]") { - set_test_name("the heading as wide as its word, the sort and the filter after it in turn"); ALScriptOutlinePane* outline = pane(); const LLRect head = outline->getChild("outline_head")->getRect(); const LLRect sort = outline->getChild("outline_sort")->getRect(); const LLRect filter = outline->getChild("outline_filter")->getRect(); - ensure("the word fits", head.getWidth() >= outline->getChild("outline_head")->getTextPixelWidth()); - ensure("in turn, apart", head.mRight < sort.mLeft && sort.mRight < filter.mLeft && filter.getWidth() >= 40); + UNSCOPED_INFO("the word fits"); + CHECK(head.getWidth() >= outline->getChild("outline_head")->getTextPixelWidth()); + { + INFO("in turn, apart"); + CHECK(head.mRight < sort.mLeft); + CHECK(sort.mRight < filter.mLeft); + CHECK(filter.getWidth() >= 40); + } } } diff --git a/indra/newview/tests/alscriptoutputpane_test.cpp b/indra/newview/tests/alscriptoutputpane_test.cpp index b5acd12c61..28d97a5a96 100644 --- a/indra/newview/tests/alscriptoutputpane_test.cpp +++ b/indra/newview/tests/alscriptoutputpane_test.cpp @@ -32,7 +32,7 @@ #include "alscriptstudio_fixture.h" -#include "../test/lltut.h" +#include #include #include @@ -88,7 +88,7 @@ namespace } } -namespace tut +namespace { struct alscriptoutputpane_data { @@ -98,15 +98,16 @@ namespace tut FakeWindow& studio = window.pane(); ALScriptOutputPane* pane = nullptr; - ALScriptOutputPane& make() + ALScriptOutputPane& require_pane() { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } // The tab as the skin built it. pane = window.find("output_tab"); - ensure("the skin builds the tab as the pane", pane != nullptr); + INFO("the skin builds the tab as the pane"); + REQUIRE(pane != nullptr); return *pane; } @@ -157,10 +158,11 @@ namespace tut return out; } // A filter chosen, as a click on it would. - void whose(const std::string& value) + void require_whose(const std::string& value) { LLComboBox* combo = window.find("output_filter"); - ensure("offered: " + value, combo->selectByValue(LLSD(value))); + CAPTURE(value); + REQUIRE(combo->selectByValue(LLSD(value))); combo->onCommit(); } void find(const std::string& words) @@ -171,41 +173,35 @@ namespace tut } const ALOutputView::Entry& last() const { return pane->view()->entries().back(); } }; - typedef test_group alscriptoutputpane_group; - typedef alscriptoutputpane_group::object alscriptoutputpane_object; - alscriptoutputpane_group alscriptoutputpane_instance("ALScriptOutputPane"); - template <> - template <> - void alscriptoutputpane_object::test<1>() + TEST_CASE_METHOD(alscriptoutputpane_data, "ALScriptOutputPane: the kind filter picks errors or owner words or debug or the studio's", "[ALScriptOutputPane]") { - set_test_name("by kind: errors, what owners were told, the debug channel, the studio's own words"); - ALScriptOutputPane& out = make(); + ALScriptOutputPane& out = require_pane(); const LLUUID door = fresh(); out.heard(said(door, "Door", "opening", Event::Channel::OwnerSay)); out.heard(said(door, "Door", "tick\n")); out.heard(failed(door, fresh(), "door.lsl", "Stack-Heap Collision", -1)); out.said("Saved.", false, nullptr, {}); - ensure_equals("everything, a trailing newline gone", shown(), std::string("|opening|tick|Stack-Heap Collision|Saved.")); + UNSCOPED_INFO("everything, a trailing newline gone"); + CHECK(shown() == std::string("|opening|tick|Stack-Heap Collision|Saved.")); out.showKind("error"); - ensure_equals("the error", shown(), std::string("|Stack-Heap Collision")); - ensure_equals("kept as chosen", out.kind(), std::string("error")); + CHECK(shown() == std::string("|Stack-Heap Collision")); + UNSCOPED_INFO("kept as chosen"); + CHECK(out.kind() == std::string("error")); out.showKind("owner"); - ensure_equals("what the owner was told", shown(), std::string("|opening")); + CHECK(shown() == std::string("|opening")); out.showKind("debug"); - ensure_equals("the debug channel", shown(), std::string("|tick")); + CHECK(shown() == std::string("|tick")); out.showKind("studio"); - ensure_equals("the studio", shown(), std::string("|Saved.")); + CHECK(shown() == std::string("|Saved.")); out.showKind(""); - ensure_equals("everything again", shownTexts().size(), size_t(4)); + UNSCOPED_INFO("everything again"); + CHECK(shownTexts().size() == size_t(4)); } - template <> - template <> - void alscriptoutputpane_object::test<2>() + TEST_CASE_METHOD(alscriptoutputpane_data, "ALScriptOutputPane: filters by whose it is and by words in either case", "[ALScriptOutputPane]") { - set_test_name("by whose: one object's alone, the scripts open here, the agent's own; and by the words, in either case"); - ALScriptOutputPane& out = make(); + ALScriptOutputPane& out = require_pane(); const LLUUID door = fresh(); const LLUUID lamp = fresh(); const LLUUID crate = fresh(); @@ -218,103 +214,122 @@ namespace tut out.heard(said(crate, "Crate", "crate tells its owner", Event::Channel::OwnerSay)); out.said("Saved door.lsl.", false, nullptr, {}); - whose(lamp.asString()); - ensure_equals("one object's alone, not the studio's", shown(), std::string("|lamp speaks")); - whose("open"); - ensure_equals("the open script's, and the studio's", shown(), std::string("|door speaks|Saved door.lsl.")); - whose("mine"); - ensure_equals("the agent's object's, what an owner was told, and the studio's", shown(), + require_whose(lamp.asString()); + UNSCOPED_INFO("one object's alone, not the studio's"); + CHECK(shown() == std::string("|lamp speaks")); + require_whose("open"); + UNSCOPED_INFO("the open script's, and the studio's"); + CHECK(shown() == std::string("|door speaks|Saved door.lsl.")); + require_whose("mine"); + UNSCOPED_INFO("the agent's object's, what an owner was told, and the studio's"); + CHECK(shown() == std::string("|lamp speaks|crate tells its owner|Saved door.lsl.")); - whose(""); + require_whose(""); find("DOOR"); - ensure_equals("the words, in the text or whose it is", shown(), std::string("|door speaks|Saved door.lsl.")); + UNSCOPED_INFO("the words, in the text or whose it is"); + CHECK(shown() == std::string("|door speaks|Saved door.lsl.")); find(" lamp "); - ensure_equals("trimmed", shown(), std::string("|lamp speaks")); + UNSCOPED_INFO("trimmed"); + CHECK(shown() == std::string("|lamp speaks")); find(""); - ensure_equals("everything", shownTexts().size(), size_t(4)); + UNSCOPED_INFO("everything"); + CHECK(shownTexts().size() == size_t(4)); // The script closed: the filter by open scripts follows. - whose("open"); + require_whose("open"); services.docs.clear(); out.openChanged(); - ensure_equals("the studio's alone", shown(), std::string("|Saved door.lsl.")); + UNSCOPED_INFO("the studio's alone"); + CHECK(shown() == std::string("|Saved door.lsl.")); } - template <> - template <> - void alscriptoutputpane_object::test<3>() + TEST_CASE_METHOD(alscriptoutputpane_data, "ALScriptOutputPane: the objects offered stop at forty dropping the stalest but the chosen", "[ALScriptOutputPane]") { - set_test_name("the objects offered stop at forty, the one least lately heard from going first, unless it is the one chosen"); - ALScriptOutputPane& out = make(); + ALScriptOutputPane& out = require_pane(); std::vector roots; for (S32 i = 0; i < 45; ++i) { roots.push_back(fresh()); out.heard(said(roots.back(), llformat("Object %02d", i), "hello")); } - ensure_equals("forty", out.objects().size(), size_t(40)); - ensure("the first five gone", out.objects().front().first == roots[5]); + REQUIRE(out.objects().size() == size_t(40)); + UNSCOPED_INFO("the first five gone"); + CHECK(out.objects().front().first == roots[5]); // Heard from again: the last to go. out.heard(said(roots[5], "Object 05", "again")); - ensure("heard again, last", out.objects().back().first == roots[5]); - ensure_equals("still forty", out.objects().size(), size_t(40)); + UNSCOPED_INFO("heard again, last"); + CHECK(out.objects().back().first == roots[5]); + REQUIRE(out.objects().size() == size_t(40)); // Renamed, and heard from under its new name. out.heard(said(roots[9], "Renamed", "again")); - ensure_equals("its new name", out.objects().back().second, std::string("Renamed")); + CHECK(out.objects().back().second == std::string("Renamed")); // The one chosen stays, least lately heard from as it is. - whose(roots[6].asString()); + require_whose(roots[6].asString()); out.heard(said(fresh(), "Newcomer", "hello")); - ensure_equals("forty still", out.objects().size(), size_t(40)); - ensure("the one chosen stayed", out.objects().front().first == roots[6]); - ensure("the next went", std::none_of(out.objects().begin(), out.objects().end(), [&](const auto& one) { return one.first == roots[7]; })); - ensure_equals("and is still chosen", window.find("output_filter")->getValue().asString(), roots[6].asString()); + REQUIRE(out.objects().size() == size_t(40)); + UNSCOPED_INFO("the one chosen stayed"); + CHECK(out.objects().front().first == roots[6]); + UNSCOPED_INFO("the next went"); + CHECK(std::none_of(out.objects().begin(), out.objects().end(), [&](const auto& one) { return one.first == roots[7]; })); + UNSCOPED_INFO("and is still chosen"); + CHECK(window.find("output_filter")->getValue().asString() == roots[6].asString()); } - template <> - template <> - void alscriptoutputpane_object::test<4>() + TEST_CASE_METHOD(alscriptoutputpane_data, "ALScriptOutputPane: an error or failure out of sight is unread until seen or cleared", "[ALScriptOutputPane]") { - set_test_name("an error or a failure said out of sight is unread until the tab is looked at, or cleared"); - ALScriptOutputPane& out = make(); + ALScriptOutputPane& out = require_pane(); const LLUUID door = fresh(); out.heard(said(door, "Door", "tick")); out.said("Saved.", false, nullptr, {}); - ensure("words alone are not unread", !out.unread()); - ensure_equals("and nothing to say of it", studio.unreadChanges, 0); + UNSCOPED_INFO("words alone are not unread"); + CHECK_FALSE(out.unread()); + UNSCOPED_INFO("and nothing to say of it"); + CHECK(studio.unreadChanges == 0); out.heard(failed(door, fresh(), "door.lsl", "Math Error", -1)); - ensure("an error is", out.unread()); - ensure_equals("said on the title", studio.unreadChanges, 1); + UNSCOPED_INFO("an error is"); + CHECK(out.unread()); + UNSCOPED_INFO("said on the title"); + CHECK(studio.unreadChanges == 1); out.pump(); - ensure("still, out of sight", out.unread()); + UNSCOPED_INFO("still, out of sight"); + CHECK(out.unread()); studio.inSight = true; out.pump(); - ensure("looked at", !out.unread()); - ensure_equals("said again", studio.unreadChanges, 2); + UNSCOPED_INFO("looked at"); + CHECK_FALSE(out.unread()); + UNSCOPED_INFO("said again"); + CHECK(studio.unreadChanges == 2); out.said("Could not save.", true, nullptr, {}); - ensure("a failure in sight is read", !out.unread()); + UNSCOPED_INFO("a failure in sight is read"); + CHECK_FALSE(out.unread()); studio.inSight = false; out.said("Could not save.", true, nullptr, {}); - ensure("a failure out of sight is not", out.unread()); + UNSCOPED_INFO("a failure out of sight is not"); + CHECK(out.unread()); window.find("output_clear")->onCommit(); - ensure("cleared", !out.unread()); - ensure("with what was said", out.view()->entries().empty()); + UNSCOPED_INFO("cleared"); + CHECK_FALSE(out.unread()); + UNSCOPED_INFO("with what was said"); + CHECK(out.view()->entries().empty()); } - template <> - template <> - void alscriptoutputpane_object::test<5>() + TEST_CASE_METHOD(alscriptoutputpane_data, "ALScriptOutputPane: the studio's words link to the tab they name and to its actions", "[ALScriptOutputPane]") { - set_test_name("the studio's words link to the tab they name and to what can be done about it, each handed to the window"); - ALScriptOutputPane& out = make(); + ALScriptOutputPane& out = require_pane(); ALScriptStudioDoc& doc = services.addDoc("door-script", ALScriptRef(fresh(), fresh()), "door.lsl"); out.said("Could not save door.lsl.", true, &doc, { "retry", "copy" }); const ALOutputView::Entry& entry = last(); - ensure_equals("the name and two things to do", entry.links.size(), size_t(3)); - ensure_equals("the name, where the words say it", entry.links[0].begin, 15); - ensure_equals("to its end", entry.links[0].end, 23); - ensure("each thing after the words", entry.text.find(services.words("ActionRetry")) > entry.text.find("door.lsl.")); - ensure_equals("where it is", entry.text.substr(entry.links[1].begin, entry.links[1].end - entry.links[1].begin), services.words("ActionRetry")); + UNSCOPED_INFO("the name and two things to do"); + REQUIRE(entry.links.size() == size_t(3)); + UNSCOPED_INFO("the name, where the words say it"); + CHECK(entry.links[0].begin == 15); + UNSCOPED_INFO("to its end"); + CHECK(entry.links[0].end == 23); + UNSCOPED_INFO("each thing after the words"); + CHECK(entry.text.find(services.words("ActionRetry")) > entry.text.find("door.lsl.")); + UNSCOPED_INFO("where it is"); + CHECK(entry.text.substr(entry.links[1].begin, entry.links[1].end - entry.links[1].begin) == services.words("ActionRetry")); ALOutputView::Entry chosen = entry; for (const ALOutputView::Entry::Link& link : entry.links) @@ -322,27 +337,28 @@ namespace tut chosen.value = link.value; out.choose(chosen); } - ensure_equals("the tab, with its problems", studio.shown.size(), size_t(1)); - ensure_equals("the tab named", studio.shown[0], std::string("door-script:problems")); - ensure_equals("what to do, by name", studio.actions.size(), size_t(2)); - ensure_equals("try again", studio.actions[0], std::string("door-script:retry")); - ensure_equals("a copy", studio.actions[1], std::string("door-script:copy")); + UNSCOPED_INFO("the tab, with its problems"); + REQUIRE(studio.shown.size() == size_t(1)); + UNSCOPED_INFO("the tab named"); + CHECK(studio.shown[0] == std::string("door-script:problems")); + UNSCOPED_INFO("what to do, by name"); + REQUIRE(studio.actions.size() == size_t(2)); + CHECK(studio.actions[0] == std::string("door-script:retry")); + CHECK(studio.actions[1] == std::string("door-script:copy")); // Said of no tab: nothing to link. out.said("Nothing open.", false, nullptr, { "retry" }); - ensure("no links", last().links.empty()); + CHECK(last().links.empty()); // The tab closed since: nothing asked of the window. services.docs.clear(); out.choose(chosen); - ensure_equals("nothing more", studio.actions.size(), size_t(2)); + UNSCOPED_INFO("nothing more"); + CHECK(studio.actions.size() == size_t(2)); } - template <> - template <> - void alscriptoutputpane_object::test<6>() + TEST_CASE_METHOD(alscriptoutputpane_data, "ALScriptOutputPane: a run's lines map back to the script or an include for each frame", "[ALScriptOutputPane]") { - set_test_name("a run's lines read back through the text it runs: the script's own, an include's, and the frames under an error"); - ALScriptOutputPane& out = make(); + ALScriptOutputPane& out = require_pane(); const LLUUID object = fresh(); const LLUUID item = fresh(); // Ten lines out: the first four the include's, the rest the @@ -367,42 +383,63 @@ namespace tut Event error = failed(object, item, "Counter", "attempt to index nil", 7); error.stack = { "lua_script:8: attempt to index nil", "lua_script:3 function helper", "lua_script:8", "[C] function error" }; const ALScriptOutputPane::Place at = out.heard(error); - ensure_equals("the error's line, the script's", at.line, 4); - ensure("in the script", at.file.empty()); + UNSCOPED_INFO("the error's line, the script's"); + CHECK(at.line == 4); + UNSCOPED_INFO("in the script"); + CHECK(at.file.empty()); const ALOutputView::Entry& entry = last(); - ensure("a link to it", entry.link); - ensure_equals("at that line", entry.value["line"].asInteger(), 4); - ensure_equals("a link for each frame of the script's", entry.links.size(), size_t(2)); - ensure_equals("the first in the include", entry.links[0].value["file"].asString(), std::string("disk:/scripts/lib.luau")); - ensure_equals("at its line", entry.links[0].value["line"].asInteger(), 2); - ensure_equals("the second in the script", entry.links[1].value["line"].asInteger(), 4); + UNSCOPED_INFO("a link to it"); + CHECK(entry.link); + UNSCOPED_INFO("at that line"); + CHECK(entry.value["line"].asInteger() == 4); + UNSCOPED_INFO("a link for each frame of the script's"); + REQUIRE(entry.links.size() == size_t(2)); + UNSCOPED_INFO("the first in the include"); + CHECK(entry.links[0].value["file"].asString() == std::string("disk:/scripts/lib.luau")); + UNSCOPED_INFO("at its line"); + CHECK(entry.links[0].value["line"].asInteger() == 2); + UNSCOPED_INFO("the second in the script"); + CHECK(entry.links[1].value["line"].asInteger() == 4); ALOutputView::Entry chosen = entry; out.choose(chosen); chosen.value = entry.links[0].value; out.choose(chosen); - ensure_equals("two places gone to", studio.went.size(), size_t(2)); - ensure("the script", studio.went[0].ref == ALScriptRef(object, item) && studio.went[0].file.empty()); - ensure_equals("at the error's line", studio.went[0].line, 4); - ensure("read back already", !studio.went[0].running); - ensure_equals("the include", studio.went[1].file, std::string("disk:/scripts/lib.luau")); - ensure_equals("by its name", studio.went[1].name, std::string("lib.luau")); - ensure_equals("at the frame's line", studio.went[1].line, 2); + UNSCOPED_INFO("two places gone to"); + REQUIRE(studio.went.size() == size_t(2)); + { + INFO("the script"); + CHECK(studio.went[0].ref == ALScriptRef(object, item)); + CHECK(studio.went[0].file.empty()); + } + UNSCOPED_INFO("at the error's line"); + CHECK(studio.went[0].line == 4); + UNSCOPED_INFO("read back already"); + CHECK_FALSE(studio.went[0].running); + UNSCOPED_INFO("the include"); + CHECK(studio.went[1].file == std::string("disk:/scripts/lib.luau")); + UNSCOPED_INFO("by its name"); + CHECK(studio.went[1].name == std::string("lib.luau")); + UNSCOPED_INFO("at the frame's line"); + CHECK(studio.went[1].line == 2); // Not open here: the line the run names, as it named it, and a // link that says so, for the window to read back once it can. services.docs.clear(); - ensure_equals("as said", out.heard(error).line, 7); + UNSCOPED_INFO("as said"); + CHECK(out.heard(error).line == 7); chosen = last(); out.choose(chosen); - ensure("gone to as the region counts it", studio.went.back().line == 7 && studio.went.back().running); + { + INFO("gone to as the region counts it"); + CHECK(studio.went.back().line == 7); + CHECK(studio.went.back().running); + } } - template<> template<> - void alscriptoutputpane_object::test<7>() + TEST_CASE_METHOD(alscriptoutputpane_data, "ALScriptOutputPane: a run's line past the envelope's lines maps back through the code", "[ALScriptOutputPane]") { - set_test_name("a run's line counted with the envelope's lines above the code, as the region counts it, read back through the code"); - ALScriptOutputPane& out = make(); + ALScriptOutputPane& out = require_pane(); const LLUUID object = fresh(); const LLUUID item = fresh(); ALSourceMap map; @@ -426,37 +463,40 @@ namespace tut envelope.compileTarget = "luau"; doc.envelope = envelope; const S32 under = envelope.codeLine(); - ensure("the envelope has lines of its own", under > 3); - ensure_equals("its code's third line, where the region names it", out.heard(failed(object, item, "Counter", "attempt to index nil", under + 2)).line, 102); - ensure_equals("a line in the envelope is none of the code's: as said", out.heard(failed(object, item, "Counter", "boom", 1)).line, 1); + UNSCOPED_INFO("the envelope has lines of its own"); + CHECK(under > 3); + UNSCOPED_INFO("its code's third line, where the region names it"); + CHECK(out.heard(failed(object, item, "Counter", "attempt to index nil", under + 2)).line == 102); + UNSCOPED_INFO("a line in the envelope is none of the code's: as said"); + CHECK(out.heard(failed(object, item, "Counter", "boom", 1)).line == 1); } - template <> - template <> - void alscriptoutputpane_object::test<8>() + TEST_CASE_METHOD(alscriptoutputpane_data, "ALScriptOutputPane: an error the filters hide is not unread", "[ALScriptOutputPane]") { - set_test_name("an error the filters hide is not unread: a stranger's, with one object's chosen; the chosen one's is"); - ALScriptOutputPane& out = make(); + ALScriptOutputPane& out = require_pane(); const LLUUID lamp = fresh(); const LLUUID crate = fresh(); out.heard(said(lamp, "Lamp", "lamp speaks")); out.heard(said(crate, "Crate", "crate speaks")); - whose(lamp.asString()); + require_whose(lamp.asString()); out.heard(failed(crate, fresh(), "crate.lsl", "Math Error", -1)); - ensure("a stranger's, hidden: not unread", !out.unread() && studio.unreadChanges == 0); + { + INFO("a stranger's, hidden: not unread"); + CHECK_FALSE(out.unread()); + CHECK(studio.unreadChanges == 0); + } out.heard(failed(lamp, fresh(), "lamp.lsl", "Math Error", -1)); - ensure("the chosen one's is", out.unread()); + UNSCOPED_INFO("the chosen one's is"); + CHECK(out.unread()); const S32 said = studio.unreadChanges; out.heard(failed(lamp, fresh(), "lamp.lsl", "Another Error", -1)); - ensure_equals("unread already: not said again", studio.unreadChanges, said); + UNSCOPED_INFO("unread already: not said again"); + CHECK(studio.unreadChanges == said); } - template <> - template <> - void alscriptoutputpane_object::test<9>() + TEST_CASE_METHOD(alscriptoutputpane_data, "ALScriptOutputPane: what objects say aloud or to the agent or in IMs gets its own kind", "[ALScriptOutputPane]") { - set_test_name("what the agent's objects say aloud, to the agent, and in IMs: each its kind, the agent's own; the prim that spoke named"); - ALScriptOutputPane& out = make(); + ALScriptOutputPane& out = require_pane(); const LLUUID lamp = fresh(); Event aloud = said(lamp, "Lamp", "hello", Event::Channel::Said); aloud.primName = "Bulb"; @@ -464,25 +504,28 @@ namespace tut out.heard(said(lamp, "Lamp", "just you", Event::Channel::SaidTo)); out.heard(said(lamp, "Lamp", "an IM", Event::Channel::Instant)); const auto& entries = out.view()->entries(); - ensure_equals("three", entries.size(), size_t(3)); - ensure_equals("the prim that spoke, after its object", entries[0].source, std::string("Lamp \xE2\x96\xB8 Bulb")); - ensure_equals("said", entries[0].kind, window.services().words("KindSaid")); - ensure_equals("said to the agent", entries[1].kind, window.services().words("KindSaidTo")); - ensure_equals("an IM", entries[2].kind, window.services().words("KindInstant")); - whose("mine"); - ensure_equals("the agent's own, all of them", shownTexts().size(), size_t(3)); - whose(""); + REQUIRE(entries.size() == size_t(3)); + UNSCOPED_INFO("the prim that spoke, after its object"); + CHECK(entries[0].source == std::string("Lamp \xE2\x96\xB8 Bulb")); + CHECK(entries[0].kind == window.services().words("KindSaid")); + UNSCOPED_INFO("said to the agent"); + CHECK(entries[1].kind == window.services().words("KindSaidTo")); + UNSCOPED_INFO("an IM"); + CHECK(entries[2].kind == window.services().words("KindInstant")); + require_whose("mine"); + UNSCOPED_INFO("the agent's own, all of them"); + CHECK(shownTexts().size() == size_t(3)); + require_whose(""); out.showKind("said"); - ensure_equals("aloud and to the agent", shown(), std::string("|hello|just you")); + UNSCOPED_INFO("aloud and to the agent"); + CHECK(shown() == std::string("|hello|just you")); out.showKind("im"); - ensure_equals("IMs", shown(), std::string("|an IM")); + CHECK(shown() == std::string("|an IM")); } - template<> template<> - void alscriptoutputpane_object::test<10>() + TEST_CASE_METHOD(alscriptoutputpane_data, "ALScriptOutputPane: the controls row keeps buttons right and narrows the lists first", "[ALScriptOutputPane]") { - set_test_name("the row over the log: the buttons against the right edge, the words' box the rest; narrow, the lists give way first, never overlapping"); - ALScriptOutputPane& out = make(); + ALScriptOutputPane& out = require_pane(); const LLRect whose = out.getChild("output_filter")->getRect(); const LLRect kind = out.getChild("output_kind")->getRect(); const auto row = [&]() { @@ -505,41 +548,72 @@ namespace tut }; out.reshape(800, out.getRect().getHeight()); std::vector wide = row(); - ensure("wide: the lists as the skin made them", wide[0].getWidth() == whose.getWidth() && wide[1].getWidth() == kind.getWidth()); - ensure("the copy against the right edge, the box up to the clear", wide[4].mRight == 798 && wide[2].mRight + 6 == wide[3].mLeft); - ensure("in order, apart", apart(wide, 800)); + { + INFO("wide: the lists as the skin made them"); + CHECK(wide[0].getWidth() == whose.getWidth()); + CHECK(wide[1].getWidth() == kind.getWidth()); + } + { + INFO("the copy against the right edge, the box up to the clear"); + CHECK(wide[4].mRight == 798); + CHECK(wide[2].mRight + 6 == wide[3].mLeft); + } + UNSCOPED_INFO("in order, apart"); + CHECK(apart(wide, 800)); out.reshape(420, out.getRect().getHeight()); std::vector narrow = row(); - ensure("narrow: the lists narrower", narrow[0].getWidth() < whose.getWidth() && narrow[1].getWidth() <= kind.getWidth()); - ensure("the buttons as they were", narrow[3].getWidth() == wide[3].getWidth() && narrow[4].getWidth() == wide[4].getWidth()); - ensure("in order, apart", apart(narrow, 420)); - ensure("the box still there", narrow[2].getWidth() >= 60); + { + INFO("narrow: the lists narrower"); + CHECK(narrow[0].getWidth() < whose.getWidth()); + CHECK(narrow[1].getWidth() <= kind.getWidth()); + } + { + INFO("the buttons as they were"); + CHECK(narrow[3].getWidth() == wide[3].getWidth()); + CHECK(narrow[4].getWidth() == wide[4].getWidth()); + } + UNSCOPED_INFO("in order, apart"); + CHECK(apart(narrow, 420)); + UNSCOPED_INFO("the box still there"); + CHECK(narrow[2].getWidth() >= 60); } - template<> template<> - void alscriptoutputpane_object::test<11>() + TEST_CASE_METHOD(alscriptoutputpane_data, "ALScriptOutputPane: a lone CR breaks a line and links follow it to the shown line", "[ALScriptOutputPane]") { - set_test_name("an error that says a lone CR runs to a line more, and a frame under it links from the line the log shows it on; the studio's own words' links likewise"); - ALScriptOutputPane& out = make(); + ALScriptOutputPane& out = require_pane(); const LLUUID object = fresh(); const LLUUID item = fresh(); Event error = failed(object, item, "Counter", "bad\rvalue", -1); error.stack = { "lua_script:8: bad\rvalue", "lua_script:3 function helper" }; out.heard(error); const ALOutputView::Entry& entry = last(); - ensure_equals("its breaks as the log reads them", entry.text, std::string("bad\nvalue\nlua_script:3 function helper")); - ensure_equals("a link for the frame", entry.links.size(), size_t(1)); - ensure_equals("on the frame's line", entry.links[0].line, 2); + UNSCOPED_INFO("its breaks as the log reads them"); + CHECK(entry.text == std::string("bad\nvalue\nlua_script:3 function helper")); + UNSCOPED_INFO("a link for the frame"); + REQUIRE(entry.links.size() == size_t(1)); + UNSCOPED_INFO("on the frame's line"); + CHECK(entry.links[0].line == 2); const ALTextDocument& shown_text = out.view()->document(); - ensure_equals("the line the log shows it on", shown_text.line(shown_text.lineCount() - 1), std::string("lua_script:3 function helper")); + UNSCOPED_INFO("the line the log shows it on"); + CHECK(shown_text.line(shown_text.lineCount() - 1) == std::string("lua_script:3 function helper")); // The studio's own words likewise: a name after a lone CR linked on // the line it is shown on, and what to do after it. ALScriptStudioDoc& doc = services.addDoc("door-script", ALScriptRef(fresh(), fresh()), "door.lsl"); out.said("Could not save\rdoor.lsl.", true, &doc, { "retry" }); const ALOutputView::Entry& studio_said = last(); - ensure_equals("the name and what to do", studio_said.links.size(), size_t(2)); - ensure("the name at the start of the second line", studio_said.links[0].line == 1 && studio_said.links[0].begin == 0 && studio_said.links[0].end == 8); - ensure("what to do on it too", studio_said.links[1].line == 1 && studio_said.links[1].begin == 12); + UNSCOPED_INFO("the name and what to do"); + REQUIRE(studio_said.links.size() == size_t(2)); + { + INFO("the name at the start of the second line"); + CHECK(studio_said.links[0].line == 1); + CHECK(studio_said.links[0].begin == 0); + CHECK(studio_said.links[0].end == 8); + } + { + INFO("what to do on it too"); + CHECK(studio_said.links[1].line == 1); + CHECK(studio_said.links[1].begin == 12); + } } } diff --git a/indra/newview/tests/alscriptproblemspane_test.cpp b/indra/newview/tests/alscriptproblemspane_test.cpp index f41eed5fce..998c2d11f0 100644 --- a/indra/newview/tests/alscriptproblemspane_test.cpp +++ b/indra/newview/tests/alscriptproblemspane_test.cpp @@ -40,7 +40,7 @@ #include "alscriptstudio_fixture.h" -#include "../test/lltut.h" +#include #include #include @@ -135,7 +135,7 @@ namespace } } -namespace tut +namespace { struct alscriptproblemspane_data { @@ -149,11 +149,12 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } // The tab as the skin built it. pane = window.find("problems_tab"); - ensure("the skin builds the tab as the pane", pane != nullptr); + UNSCOPED_INFO("the skin builds the tab as the pane"); + REQUIRE(pane != nullptr); return *pane; } @@ -225,7 +226,7 @@ namespace tut return; } } - fail("no row says " + message); + FAIL("no row says " + message); } void tick(const char* box, bool on) { @@ -236,19 +237,14 @@ namespace tut void pick(const char* combo, const std::string& value) { LLComboBox* box = window.find(combo); - ensure("offered: " + value, box->selectByValue(LLSD(value))); + INFO("the combo offers " << value); + REQUIRE(box->selectByValue(LLSD(value))); box->onCommit(); } }; - typedef test_group alscriptproblemspane_group; - typedef alscriptproblemspane_group::object alscriptproblemspane_object; - alscriptproblemspane_group alscriptproblemspane_instance("ALScriptProblemsPane"); - template <> - template <> - void alscriptproblemspane_object::test<1>() + TEST_CASE_METHOD(alscriptproblemspane_data, "ALScriptProblemsPane: gathered problems come in order and fixes lapse once the text changes", "[ALScriptProblemsPane]") { - set_test_name("a tab's problems gathered: in order, each at its level, the compiler's under the analyzer's, and fixes only for the text they were made on"); make(); Doc& d = doc("door"); using S = ALScriptProblem::Source; @@ -282,16 +278,25 @@ namespace tut { messages.push_back(row.message); } - ensure_equals("six rows", made.rows.size(), size_t(6)); - ensure_equals("the weight first, at no line", made.rows[0].origin, services.words("OriginWeight")); - ensure("the weight a warning, the studio's reckoning", made.rows[0].level == Doc::Level::Warning); - ensure_equals("then the compiler's own", made.rows[1].message, std::string("the compiler's own")); - ensure_equals("the lint with its name", made.rows[2].message, std::string("a local shadows another [LocalShadow]")); - ensure_equals("the analyzer's error, the compiler's on its line said once", made.rows[3].message, std::string("syntax error, unexpected")); - ensure_equals("the run-time error, how many times", made.rows[4].message, "Math Error " + services.words("RepeatedTimes", { { "[COUNT]", "3" } })); - ensure("a run-time error is an error", made.rows[4].level == Doc::Level::Error); - ensure_equals("the include's last", made.rows[5].fileName, std::string("lib.lsl")); - ensure_equals("the squiggles: the script's own", made.decorations.size(), size_t(4)); + REQUIRE(made.rows.size() == size_t(6)); + UNSCOPED_INFO("the weight first, at no line"); + CHECK(made.rows[0].origin == services.words("OriginWeight")); + UNSCOPED_INFO("the weight a warning, the studio's reckoning"); + CHECK(made.rows[0].level == Doc::Level::Warning); + UNSCOPED_INFO("then the compiler's own"); + CHECK(made.rows[1].message == std::string("the compiler's own")); + UNSCOPED_INFO("the lint with its name"); + CHECK(made.rows[2].message == std::string("a local shadows another [LocalShadow]")); + UNSCOPED_INFO("the analyzer's error, the compiler's on its line said once"); + CHECK(made.rows[3].message == std::string("syntax error, unexpected")); + UNSCOPED_INFO("the run-time error, how many times"); + CHECK(made.rows[4].message == "Math Error " + services.words("RepeatedTimes", { { "[COUNT]", "3" } })); + UNSCOPED_INFO("a run-time error is an error"); + CHECK(made.rows[4].level == Doc::Level::Error); + UNSCOPED_INFO("the include's last"); + CHECK(made.rows[5].fileName == std::string("lib.lsl")); + UNSCOPED_INFO("the squiggles: the script's own"); + REQUIRE(made.decorations.size() == size_t(4)); const auto underline = [&](S32 line) { for (const ALCodeEditor::Decoration& each : made.decorations) { @@ -303,32 +308,36 @@ namespace tut return ALCodeEditor::Decoration::Style::Background; }; using Style = ALCodeEditor::Decoration::Style; - ensure("an error waved, a warning dashed, a run-time error waved", - underline(2) == Style::Squiggle && underline(4) == Style::Dashed && underline(8) == Style::Squiggle); - ensure("the run-time mark on its line", - std::find(made.marks.begin(), made.marks.end(), std::make_pair(S32(8), ALCodeEditor::Mark::Runtime)) != made.marks.end()); - ensure("each level's mark", ALScriptProblemsPane::markOf(Doc::Level::Error) == ALCodeEditor::Mark::Error && - ALScriptProblemsPane::markOf(Doc::Level::Warning) == ALCodeEditor::Mark::Warning && - ALScriptProblemsPane::markOf(Doc::Level::Note) == ALCodeEditor::Mark::Note); - ensure_equals("the lint's line offers its fix", made.fixable.size(), size_t(1)); - ensure("which changes the script", made.fixable[0] == std::make_pair(S32(4), true)); + { + INFO("an error waved, a warning dashed, a run-time error waved"); + CHECK(underline(2) == Style::Squiggle); + CHECK(underline(4) == Style::Dashed); + CHECK(underline(8) == Style::Squiggle); + } + UNSCOPED_INFO("the run-time mark on its line"); + REQUIRE(std::find(made.marks.begin(), made.marks.end(), std::make_pair(S32(8), ALCodeEditor::Mark::Runtime)) != made.marks.end()); + CHECK(ALScriptProblemsPane::markOf(Doc::Level::Error) == ALCodeEditor::Mark::Error); + CHECK(ALScriptProblemsPane::markOf(Doc::Level::Warning) == ALCodeEditor::Mark::Warning); + CHECK(ALScriptProblemsPane::markOf(Doc::Level::Note) == ALCodeEditor::Mark::Note); + UNSCOPED_INFO("the lint's line offers its fix"); + REQUIRE(made.fixable.size() == size_t(1)); + UNSCOPED_INFO("which changes the script"); + CHECK(made.fixable[0] == std::make_pair(S32(4), true)); // Typed in since the check: the fixes are of a text there is no // longer, and the compiler's error on the analyzer's line is its own // again. d.editor->setText("line 0\nline 1\n"); made = gather(d); - ensure("no fix offered", made.fixable.empty()); - ensure("the compiler's error said", std::any_of(made.rows.begin(), made.rows.end(), [](const Doc::Shown& row) { return row.message == "syntax error"; })); - ensure("nor the weight, weighed of the old text", - std::none_of(made.rows.begin(), made.rows.end(), [&](const Doc::Shown& row) { return row.origin == services.words("OriginWeight"); })); + REQUIRE(made.fixable.empty()); + UNSCOPED_INFO("the compiler's error said"); + CHECK(std::any_of(made.rows.begin(), made.rows.end(), [](const Doc::Shown& row) { return row.message == "syntax error"; })); + UNSCOPED_INFO("nor the weight, weighed of the old text"); + CHECK(std::none_of(made.rows.begin(), made.rows.end(), [&](const Doc::Shown& row) { return row.origin == services.words("OriginWeight"); })); } - template <> - template <> - void alscriptproblemspane_object::test<2>() + TEST_CASE_METHOD(alscriptproblemspane_data, "ALScriptProblemsPane: the list follows its filters and scope and the boxes say the counts", "[ALScriptProblemsPane]") { - set_test_name("the list through its filters -- levels, origin, words -- and its scope, with the counts the boxes and the tab say"); ALScriptProblemsPane& out = make(); using S = ALScriptProblem::Source; using V = ALScriptProblem::Severity; @@ -340,63 +349,76 @@ namespace tut gather(lamp); gather(door); out.fill(&door); - ensure_equals("the one in front's", shown(), std::string("|door error|door warning|door note")); - ensure_equals("three to list", out.held(), 3); - ensure("said on the tab", studio.counts > 0); - ensure_equals("counted on the box", window.find("problems_errors")->getLabel(), + UNSCOPED_INFO("the one in front's"); + CHECK(shown() == std::string("|door error|door warning|door note")); + CHECK(out.held() == 3); + UNSCOPED_INFO("said on the tab"); + CHECK(studio.counts > 0); + UNSCOPED_INFO("counted on the box"); + CHECK(window.find("problems_errors")->getLabel() == services.words("FilterErrorsCount", { { "[COUNT]", "1" } })); tick("problems_errors", false); - ensure("the filter kept", studio.filtersKept == 1); - ensure_equals("no errors, and what is hidden said", shown(), + UNSCOPED_INFO("the filter kept"); + CHECK(studio.filtersKept == 1); + UNSCOPED_INFO("no errors, and what is hidden said"); + CHECK(shown() == "|door warning|door note|#" + services.counted("ProblemsHidden", 1)); tick("problems_errors", true); pick("problems_origin", services.words("OriginLint")); - ensure_equals("the lint's alone", shown(), "|door warning|door note|#" + services.counted("ProblemsHidden", 1)); + UNSCOPED_INFO("the lint's alone"); + CHECK(shown() == "|door warning|door note|#" + services.counted("ProblemsHidden", 1)); pick("problems_origin", ""); window.find("problems_filter")->setText(std::string("NOTE")); window.find("problems_filter")->onCommit(); - ensure_equals("the words, in either case", shown(), "|door note|#" + services.counted("ProblemsHidden", 2)); - ensure("rows to show: nothing said over them", !out.list()->saysEmpty()); + UNSCOPED_INFO("the words, in either case"); + CHECK(shown() == "|door note|#" + services.counted("ProblemsHidden", 2)); + UNSCOPED_INFO("rows to show: nothing said over them"); + CHECK_FALSE(out.list()->saysEmpty()); window.find("problems_filter")->setText(std::string()); window.find("problems_filter")->onCommit(); pick("problems_scope", "all"); - ensure_equals("every script's, each under its name", shown(), + UNSCOPED_INFO("every script's, each under its name"); + CHECK(shown() == "|#door.lsl \xC2\xB7 " + services.counted("ProblemErrors", 1) + ", " + services.counted("ProblemWarnings", 1) + ", " + services.counted("ProblemNotes", 1) + "|door error|door warning|door note|#lamp.lsl \xC2\xB7 " + services.counted("ProblemErrors", 1) + "|lamp error"); - ensure_equals("four to list", out.held(), 4); + CHECK(out.held() == 4); // Another script's problems gathered again: listed, since every // one's are. lamp.check->analysis.push_back(problem(S::Lint, V::Warning, 7, "lamp warning")); gather(lamp); - ensure("the new one listed", shown().find("lamp warning") != std::string::npos); + UNSCOPED_INFO("the new one listed"); + REQUIRE(shown().find("lamp warning") != std::string::npos); pick("problems_scope", "this"); - ensure_equals("back to the one", shown(), std::string("|door error|door warning|door note")); + UNSCOPED_INFO("back to the one"); + CHECK(shown() == std::string("|door error|door warning|door note")); gather(lamp); - ensure("another's gathered leaves the list alone", shown().find("lamp") == std::string::npos); + UNSCOPED_INFO("another's gathered leaves the list alone"); + REQUIRE(shown().find("lamp") == std::string::npos); // Closed, it is let go of. out.closed("door"); - ensure("forgotten", out.listedId().empty()); + REQUIRE(out.listedId().empty()); LLSD state; out.saveState(state); - ensure_equals("the scope kept", state["problem_scope"].asString(), std::string("this")); + UNSCOPED_INFO("the scope kept"); + CHECK(state["problem_scope"].asString() == std::string("this")); // A script with nothing wrong with it says so where the rows would be. Doc& clean = doc("clean"); gather(clean); out.fill(&clean); - ensure("nothing listed, said", out.list()->saysEmpty() - && out.list()->emptyWords() == services.words("NoProblemsIn", { { "[NAME]", "clean.lsl" } })); + { + INFO("nothing listed, said"); + REQUIRE(out.list()->saysEmpty()); + CHECK(out.list()->emptyWords() == services.words("NoProblemsIn", { { "[NAME]", "clean.lsl" } })); + } } - template <> - template <> - void alscriptproblemspane_object::test<3>() + TEST_CASE_METHOD(alscriptproblemspane_data, "ALScriptProblemsPane: selectFirstError and a chosen row go to the problem's place", "[ALScriptProblemsPane]") { - set_test_name("the first error chosen and gone to, passing over the compiler's where asked; a row chosen goes to its place"); ALScriptProblemsPane& out = make(); Doc& d = doc("door"); d.problems.push_back({ 1, 0, true, std::string(), "ERROR", "compiler first" }); @@ -405,30 +427,42 @@ namespace tut gather(d); out.fill(&d); out.selectFirstError(false); - ensure_equals("the first error, the compiler's", studio.chosen.back().line, 1); - ensure("taken to the script", studio.toEditor.back()); + UNSCOPED_INFO("the first error, the compiler's"); + CHECK(studio.chosen.back().line == 1); + UNSCOPED_INFO("taken to the script"); + REQUIRE(studio.toEditor.back()); out.selectFirstError(true); - ensure_equals("the checkers' own", studio.chosen.back().line, 5); - ensure_equals("its stretch", studio.chosen.back().endColumn, 6); + UNSCOPED_INFO("the checkers' own"); + CHECK(studio.chosen.back().line == 5); + CHECK(studio.chosen.back().endColumn == 6); choose("a warning before"); out.choose(false); - ensure_equals("a row walked to", studio.chosen.back().line, 0); - ensure("the keyboard left in the list", !studio.toEditor.back()); - ensure_equals("whose it is", studio.chosen.back().doc, std::string("door")); + UNSCOPED_INFO("a row walked to"); + CHECK(studio.chosen.back().line == 0); + UNSCOPED_INFO("the keyboard left in the list"); + REQUIRE_FALSE(studio.toEditor.back()); + CHECK(studio.chosen.back().doc == std::string("door")); // The list's keys: return goes there to type, escape back to the // script without going. - ensure("return", out.list()->handleKeyHere(KEY_RETURN, MASK_NONE) && studio.chosen.back().line == 0 && studio.toEditor.back()); + { + INFO("return"); + CHECK(out.list()->handleKeyHere(KEY_RETURN, MASK_NONE)); + CHECK(studio.chosen.back().line == 0); + REQUIRE(studio.toEditor.back()); + } const size_t gone = studio.chosen.size(); - ensure("escape", out.list()->handleKeyHere(KEY_ESCAPE, MASK_NONE) && services.reveals.back() && studio.chosen.size() == gone); + { + INFO("escape"); + CHECK(out.list()->handleKeyHere(KEY_ESCAPE, MASK_NONE)); + REQUIRE(services.reveals.back()); + REQUIRE(studio.chosen.size() == gone); + } } - template <> - template <> - void alscriptproblemspane_object::test<4>() + TEST_CASE_METHOD(alscriptproblemspane_data, "ALScriptProblemsPane: the menu offers only what the chosen problem allows and acts on it", "[ALScriptProblemsPane]") { - set_test_name("the menu offers what the chosen problem allows: its lint turned off or made an error, its fixes, every one of its kind"); ALScriptProblemsPane& out = make(); Doc& d = doc("door"); studio.lints.insert("LocalShadow"); @@ -447,51 +481,75 @@ namespace tut out.fill(&d); choose("no lint"); - ensure("no lint to turn off", !out.enabled("off") && !out.enabled("error")); - ensure("copying always", out.enabled("copy") && out.enabled("copy_all")); - ensure("no run-time errors to let go of", !out.enabled("clear_runtime")); - ensure("of no kind, none to fix along with it", !out.fixShown("kind")); + { + INFO("no lint to turn off"); + CHECK_FALSE(out.enabled("off")); + CHECK_FALSE(out.enabled("error")); + } + { + INFO("copying always"); + CHECK(out.enabled("copy")); + CHECK(out.enabled("copy_all")); + } + UNSCOPED_INFO("no run-time errors to let go of"); + CHECK_FALSE(out.enabled("clear_runtime")); + UNSCOPED_INFO("of no kind, none to fix along with it"); + CHECK_FALSE(out.fixShown("kind")); out.act("fix_kind"); - ensure("nor made", studio.kinds.empty()); - ensure("Fix All, over two", out.fixShown("all")); + UNSCOPED_INFO("nor made"); + REQUIRE(studio.kinds.empty()); + UNSCOPED_INFO("Fix All, over two"); + CHECK(out.fixShown("all")); choose("shadowed once [LocalShadow]"); - ensure("its lint", out.enabled("off") && out.enabled("error")); - ensure("a warning", !out.lintIsError()); + { + INFO("its lint"); + CHECK(out.enabled("off")); + CHECK(out.enabled("error")); + } + UNSCOPED_INFO("a warning"); + CHECK_FALSE(out.lintIsError()); out.act("error"); - ensure("made an error", out.lintIsError()); + UNSCOPED_INFO("made an error"); + CHECK(out.lintIsError()); out.act("error"); - ensure("and back", !out.lintIsError()); + UNSCOPED_INFO("and back"); + CHECK_FALSE(out.lintIsError()); out.act("off"); - ensure("turned off", studio.levels["LocalShadow"] == ALScriptLints::Level::Off); + CHECK(studio.levels["LocalShadow"] == ALScriptLints::Level::Off); out.act("settings"); - ensure_equals("the settings", studio.settings, 1); - ensure("every one of its kind", out.fixShown("kind")); + CHECK(studio.settings == 1); + UNSCOPED_INFO("every one of its kind"); + CHECK(out.fixShown("kind")); out.act("fix:0"); - ensure_equals("its fix made", studio.fixed.size(), size_t(1)); - ensure_equals("which", studio.fixed[0], std::string("door:Rename")); + UNSCOPED_INFO("its fix made"); + REQUIRE(studio.fixed.size() == size_t(1)); + CHECK(studio.fixed[0] == std::string("door:Rename")); out.act("fix_kind"); out.act("fix_all"); - ensure_equals("of its kind, then of all", studio.kinds.size(), size_t(2)); - ensure_equals("by its kind", studio.kinds[0], std::string("door:shadow")); - ensure_equals("then every kind", studio.kinds[1], std::string("door:")); + UNSCOPED_INFO("of its kind, then of all"); + REQUIRE(studio.kinds.size() == size_t(2)); + CHECK(studio.kinds[0] == std::string("door:shadow")); + UNSCOPED_INFO("then every kind"); + CHECK(studio.kinds[1] == std::string("door:")); out.act("copy_all"); - ensure_equals("copied, and said", services.statuses.back(), services.counted("ProblemsCopied", 3)); + UNSCOPED_INFO("copied, and said"); + CHECK(services.statuses.back() == services.counted("ProblemsCopied", 3)); d.runtime.push_back({ 1, -1, std::string(), "Math Error", 1 }); - ensure("run-time errors to let go of", out.enabled("clear_runtime")); + UNSCOPED_INFO("run-time errors to let go of"); + CHECK(out.enabled("clear_runtime")); out.act("clear_runtime"); - ensure("let go of", d.runtime.empty()); - ensure("and not recalled when it is opened again", studio.cleared == std::vector{ d.id }); - ensure_equals("and the tab's problems gathered again", studio.refreshed.size(), size_t(1)); + REQUIRE(d.runtime.empty()); + UNSCOPED_INFO("and not recalled when it is opened again"); + CHECK(studio.cleared == std::vector{ d.id }); + UNSCOPED_INFO("and the tab's problems gathered again"); + REQUIRE(studio.refreshed.size() == size_t(1)); } - template <> - template <> - void alscriptproblemspane_object::test<5>() + TEST_CASE_METHOD(alscriptproblemspane_data, "ALScriptProblemsPane: a compiler error a line after the analyzer's is merged and dated after edits", "[ALScriptProblemsPane]") { - set_test_name("the compiler's error a line from the analyzer's is the same error said once; the compiler's said to be as of the last save once the text has changed"); make(); Doc& d = doc("door"); using S = ALScriptProblem::Source; @@ -507,8 +565,10 @@ namespace tut { messages.push_back(row.message); } - ensure("the one a line after said once", std::find(messages.begin(), messages.end(), "syntax error") == messages.end()); - ensure("one further off kept", std::find(messages.begin(), messages.end(), "far from any") != messages.end()); + UNSCOPED_INFO("the one a line after said once"); + REQUIRE(std::find(messages.begin(), messages.end(), "syntax error") == messages.end()); + UNSCOPED_INFO("one further off kept"); + REQUIRE(std::find(messages.begin(), messages.end(), "far from any") != messages.end()); // Typed in since the save: what the compiler said is of the text // as it was. d.editor->insertText("x"); @@ -518,14 +578,12 @@ namespace tut { marked |= row.message == "far from any " + services.words("CompilerAsSaved"); } - ensure("as of the last save", marked); + UNSCOPED_INFO("as of the last save"); + CHECK(marked); } - template <> - template <> - void alscriptproblemspane_object::test<6>() + TEST_CASE_METHOD(alscriptproblemspane_data, "ALScriptProblemsPane: an LSL lint shows its NOLINT name and an error no code number", "[ALScriptProblemsPane]") { - set_test_name("an LSL lint says its name, which a NOLINT comment turns it off by; an error says no number; the origins offer the weight"); make(); Doc& d = doc("door"); using S = ALScriptProblem::Source; @@ -544,17 +602,20 @@ namespace tut named |= row.message == "declared but never used [DeclaredButNotUsed]"; numbered |= row.message.find("10002") != std::string::npos; } - ensure("the lint named", named); - ensure("the error no number", !numbered); + UNSCOPED_INFO("the lint named"); + CHECK(named); + UNSCOPED_INFO("the error no number"); + CHECK_FALSE(numbered); LLComboBox* origins = window.find("problems_origin"); - ensure("the origins offer the weight", origins && origins->selectByValue(LLSD(services.words("OriginWeight")))); + { + INFO("the origins offer the weight"); + REQUIRE(origins); + CHECK(origins->selectByValue(LLSD(services.words("OriginWeight")))); + } } - template <> - template <> - void alscriptproblemspane_object::test<7>() + TEST_CASE_METHOD(alscriptproblemspane_data, "ALScriptProblemsPane: code past four fifths of its limit warns what is left once optimized", "[ALScriptProblemsPane]") { - set_test_name("code past four fifths of its target's limit is a warning that says what is left to run in; not before the optimizer, nor under"); make(); Doc& d = doc("door"); ALScriptWeight weight; @@ -570,39 +631,43 @@ namespace tut return rows; }; std::vector rows = weights(gather(d)); - ensure_equals("near: one row", rows.size(), size_t(1)); - ensure("a warning", rows[0].level == Doc::Level::Warning); - ensure("what is left", rows[0].message == services.words("WeightNear", { { "[SIZE]", "14.0" }, { "[LIMIT]", "16" }, { "[LEFT]", "2.0" }, { "[TARGET]", "LSL (LSO)" } })); + UNSCOPED_INFO("near: one row"); + REQUIRE(rows.size() == size_t(1)); + CHECK(rows[0].level == Doc::Level::Warning); + UNSCOPED_INFO("what is left"); + CHECK(rows[0].message == services.words("WeightNear", { { "[SIZE]", "14.0" }, { "[LIMIT]", "16" }, { "[LEFT]", "2.0" }, { "[TARGET]", "LSL (LSO)" } })); d.weighing->exact = false; - ensure("before the optimizer, nothing", weights(gather(d)).empty()); + UNSCOPED_INFO("before the optimizer, nothing"); + REQUIRE(weights(gather(d)).empty()); d.weighing->exact = true; d.weighing->weight->total = 12 * 1024; - ensure("at three quarters, nothing", weights(gather(d)).empty()); + UNSCOPED_INFO("at three quarters, nothing"); + REQUIRE(weights(gather(d)).empty()); d.weighing->weight->total = 17 * 1024; rows = weights(gather(d)); - ensure("over: the over row alone", rows.size() == 1 && rows[0].message.find("more than") != std::string::npos); + { + INFO("over: the over row alone"); + REQUIRE(rows.size() == 1); + REQUIRE(rows[0].message.find("more than") != std::string::npos); + } } - template <> - template <> - void alscriptproblemspane_object::test<8>() + TEST_CASE_METHOD(alscriptproblemspane_data, "ALScriptProblemsPane: a problem in generated code is listed as such and not marked in the source", "[ALScriptProblemsPane]") { - set_test_name("a problem in code the preprocessor made is listed under that name, not marked on the source's line of its number"); make(); Doc& d = doc("door"); ALScriptProblem made_here = problem(ALScriptProblem::Source::Parser, ALScriptProblem::Severity::Error, 1, "in a switch's jump table", Doc::GENERATED); d.check->analysis = { made_here }; d.check->analysisVersion = d.editor->document().version(); const ALScriptProblemsPane::Made made = gather(d); - ensure_equals("one row", made.rows.size(), size_t(1)); - ensure_equals("named so", made.rows[0].fileName, services.words("InGeneratedCode")); - ensure("no mark, no squiggle", made.marks.empty() && made.decorations.empty()); + REQUIRE(made.rows.size() == size_t(1)); + CHECK(made.rows[0].fileName == services.words("InGeneratedCode")); + REQUIRE(made.marks.empty()); + REQUIRE(made.decorations.empty()); } - template<> template<> - void alscriptproblemspane_object::test<9>() + TEST_CASE_METHOD(alscriptproblemspane_data, "ALScriptProblemsPane: a checked script with no tab is listed after open ones and chosen by its item", "[ALScriptProblemsPane]") { - set_test_name("a script no tab holds, which an object's check reached, is listed after the open ones under its name, chosen by its item, and gives way to its tab"); make(); Doc& d = doc("door"); d.check->analysis = { problem(ALScriptProblem::Source::Parser, ALScriptProblem::Severity::Error, 1, "in the open one") }; @@ -622,10 +687,14 @@ namespace tut pane->showEveryScript(); const std::string listed = shown(); const std::string heading = "#" + services.words("ProblemsChecked", { { "[NAME]", "hinge.lsl" }, { "[OBJECT]", "House" } }); - ensure("the open one's, then the closed one's under its name and object: " + listed, - listed.find("in the open one") < listed.find(heading) && listed.find(heading) < listed.find("in the closed one") && - listed.find("in the closed one") != std::string::npos); - ensure_equals("counted with the rest", pane->held(), 2); + { + INFO("the open one's, then the closed one's under its name and object: " + listed); + CHECK(listed.find("in the open one") < listed.find(heading)); + CHECK(listed.find(heading) < listed.find("in the closed one")); + REQUIRE(listed.find("in the closed one") != std::string::npos); + } + UNSCOPED_INFO("counted with the rest"); + CHECK(pane->held() == 2); // Chosen by its item and name, with no tab. const std::vector rows = list()->getAllData(); @@ -634,26 +703,40 @@ namespace tut { at = rows[i]->getValue()["owner"].asString().rfind("checked:", 0) == 0 ? static_cast(i) : at; } - ensure("a row of it", at >= 0); + UNSCOPED_INFO("a row of it"); + CHECK(at >= 0); list()->selectNthItem(at); pane->choose(false); - ensure("chosen", !studio.chosen.empty()); + REQUIRE_FALSE(studio.chosen.empty()); const ALScriptProblemsPane::Place& place = studio.chosen.back(); - ensure("by its item, with no tab", place.ref == closed && place.name == "hinge.lsl" && place.doc.empty() && place.line == 3); + { + INFO("by its item, with no tab"); + CHECK(place.ref == closed); + CHECK(place.name == "hinge.lsl"); + REQUIRE(place.doc.empty()); + CHECK(place.line == 3); + } // Opened, its tab says; a new check lets the last one's go. pane->forgetChecked(closed); - ensure("given way", shown().find("in the closed one") == std::string::npos && pane->held() == 1); + { + INFO("given way"); + REQUIRE(shown().find("in the closed one") == std::string::npos); + CHECK(pane->held() == 1); + } pane->checkedScript(closed, "hinge.lsl", false, { row }, "House"); - ensure_equals("back", pane->checkedCount(), size_t(1)); + UNSCOPED_INFO("back"); + CHECK(pane->checkedCount() == size_t(1)); pane->clearChecked(); - ensure("let go of", pane->checkedCount() == 0 && shown().find("in the closed one") == std::string::npos); + { + INFO("let go of"); + CHECK(pane->checkedCount() == 0); + REQUIRE(shown().find("in the closed one") == std::string::npos); + } } - template<> template<> - void alscriptproblemspane_object::test<10>() + TEST_CASE_METHOD(alscriptproblemspane_data, "ALScriptProblemsPane: a closed script's last recompile errors list beside its check until clean", "[ALScriptProblemsPane]") { - set_test_name("what a recompile's compiler said of a closed script is listed beside its check's rows, the last recompile's alone, and goes when it compiles clean"); make(); LLUUID object, item; object.generate(); @@ -669,33 +752,40 @@ namespace tut }; pane->showEveryScript(); pane->compiledScript(closed, "hinge.lsl", false, {}, "House"); - ensure("clean, and nothing listed of it: nothing to list", pane->checkedCount() == 0); + UNSCOPED_INFO("clean, and nothing listed of it: nothing to list"); + CHECK(pane->checkedCount() == 0); pane->checkedScript(closed, "hinge.lsl", false, { row("the check's", "Lint") }, "House"); pane->compiledScript(closed, "hinge.lsl", false, { row("the compiler's", "Compiler") }, "House"); - ensure("both listed", shown().find("the check's") != std::string::npos && shown().find("the compiler's") != std::string::npos); - ensure_equals("counted", pane->held(), 2); + REQUIRE(shown().find("the check's") != std::string::npos); + REQUIRE(shown().find("the compiler's") != std::string::npos); + CHECK(pane->held() == 2); pane->compiledScript(closed, "hinge.lsl", false, { row("again", "Compiler") }, "House"); - ensure("the last recompile's alone", shown().find("the compiler's") == std::string::npos && shown().find("again") != std::string::npos); + { + INFO("the last recompile's alone"); + REQUIRE(shown().find("the compiler's") == std::string::npos); + REQUIRE(shown().find("again") != std::string::npos); + } pane->compiledScript(closed, "hinge.lsl", false, {}, "House"); - ensure("clean: the compiler's gone, the check's stay", shown().find("again") == std::string::npos && shown().find("the check's") != std::string::npos); + { + INFO("clean: the compiler's gone, the check's stay"); + REQUIRE(shown().find("again") == std::string::npos); + REQUIRE(shown().find("the check's") != std::string::npos); + } // Recompiled alone, with no check: gone with its rows. LLUUID other; other.generate(); const ALScriptRef lone(object, other); pane->compiledScript(lone, "latch.lsl", false, { row("latch", "Compiler") }, "House"); - ensure_equals("listed", pane->checkedCount(), size_t(2)); + CHECK(pane->checkedCount() == size_t(2)); pane->compiledScript(lone, "latch.lsl", false, {}, "House"); - ensure_equals("gone", pane->checkedCount(), size_t(1)); + CHECK(pane->checkedCount() == size_t(1)); } - template <> - template <> - void alscriptproblemspane_object::test<11>() + TEST_CASE_METHOD(alscriptproblemspane_data, "ALScriptProblemsPane: out of sight the pane counts at once but lists rows only when seen or chosen", "[ALScriptProblemsPane]") { - set_test_name("out of sight, the counts said at once and the rows listed once the list is seen, or once a row is to be chosen"); ALScriptProblemsPane& out = make(); using S = ALScriptProblem::Source; using V = ALScriptProblem::Severity; @@ -703,33 +793,46 @@ namespace tut Doc& door = doc("door"); door.check->analysis = { problem(S::Parser, V::Error, 1, "door error"), problem(S::Lint, V::Warning, 3, "door warning") }; tabs->selectTabByName("references_tab"); - ensure("out of sight", !ALPaneFolds::inSight(&out)); + CHECK_FALSE(ALPaneFolds::inSight(&out)); const S32 said = studio.counts; gather(door); - ensure("counted, and said on the tab", out.held() == 2 && studio.counts > said); - ensure_equals("not listed", shown(), std::string()); + { + INFO("counted, and said on the tab"); + CHECK(out.held() == 2); + CHECK(studio.counts > said); + } + UNSCOPED_INFO("not listed"); + CHECK(shown() == std::string()); out.pump(); - ensure_equals("nor while unseen", shown(), std::string()); + UNSCOPED_INFO("nor while unseen"); + CHECK(shown() == std::string()); tabs->selectTabByName("problems_tab"); out.pump(); - ensure_equals("seen: listed", shown(), std::string("|door error|door warning")); + UNSCOPED_INFO("seen: listed"); + CHECK(shown() == std::string("|door error|door warning")); tabs->selectTabByName("references_tab"); door.check->analysis.push_back(problem(S::Parser, V::Error, 5, "a second error")); gather(door); - ensure("counted again, the rows as they were", out.held() == 3 && shown() == "|door error|door warning"); + { + INFO("counted again, the rows as they were"); + CHECK(out.held() == 3); + CHECK(shown() == "|door error|door warning"); + } const size_t chosen = studio.chosen.size(); out.selectFirstError(false); - ensure("a row to choose: listed first", shown().find("a second error") != std::string::npos); - ensure("and chosen", studio.chosen.size() == chosen + 1 && studio.chosen.back().line == 1); + UNSCOPED_INFO("a row to choose: listed first"); + REQUIRE(shown().find("a second error") != std::string::npos); + { + INFO("and chosen"); + REQUIRE(studio.chosen.size() == chosen + 1); + CHECK(studio.chosen.back().line == 1); + } tabs->selectTabByName("problems_tab"); } - template <> - template <> - void alscriptproblemspane_object::test<12>() + TEST_CASE_METHOD(alscriptproblemspane_data, "ALScriptProblemsPane: a refill keeps matching rows and the selection and lists at most a thousand", "[ALScriptProblemsPane]") { - set_test_name("a refill keeps the rows that say the same, moved or not, and the row chosen; no more than a thousand listed, the rest counted"); ALScriptProblemsPane& out = make(); using S = ALScriptProblem::Source; using V = ALScriptProblem::Severity; @@ -747,13 +850,17 @@ namespace tut door.check->analysis = { problem(S::Parser, V::Error, 1, "first"), problem(S::Lint, V::Warning, 3, "second") }; gather(door); LLScrollListItem* second = row_saying("second"); - ensure("listed", second != nullptr && second->getColumn(3)->getValue().asString().rfind("4:", 0) == 0); + REQUIRE(second != nullptr); + CHECK(second->getColumn(3)->getValue().asString().rfind("4:", 0) == 0); choose("second"); door.check->analysis = { problem(S::Parser, V::Error, 2, "first"), problem(S::Lint, V::Warning, 4, "second") }; gather(door); - ensure("moved a line: the same row", row_saying("second") == second); - ensure("where it is now", second->getColumn(3)->getValue().asString().rfind("5:", 0) == 0); - ensure("still chosen", list()->getFirstSelected() == second); + UNSCOPED_INFO("moved a line: the same row"); + REQUIRE(row_saying("second") == second); + UNSCOPED_INFO("where it is now"); + CHECK(second->getColumn(3)->getValue().asString().rfind("5:", 0) == 0); + UNSCOPED_INFO("still chosen"); + CHECK(list()->getFirstSelected() == second); std::vector many; for (S32 i = 0; i < 1005; ++i) @@ -762,20 +869,20 @@ namespace tut } door.check->analysis = many; gather(door); - ensure_equals("all counted", out.held(), 1005); + UNSCOPED_INFO("all counted"); + CHECK(out.held() == 1005); S32 rows = 0; for (LLScrollListItem* item : list()->getAllData()) { rows += item->getValue().has("heading") ? 0 : 1; } - ensure_equals("a thousand listed", rows, 1000); - ensure("the rest said", shown().find(services.counted("ProblemsUnlisted", 5)) != std::string::npos); + CHECK(rows == 1000); + UNSCOPED_INFO("the rest said"); + REQUIRE(shown().find(services.counted("ProblemsUnlisted", 5)) != std::string::npos); } - template<> template<> - void alscriptproblemspane_object::test<13>() + TEST_CASE_METHOD(alscriptproblemspane_data, "ALScriptProblemsPane: showObject lists that object's open and checked scripts and no other's", "[ALScriptProblemsPane]") { - set_test_name("an object's check lists that object's scripts: its open ones and those it read, none of another object's open beside them"); make(); const LLUUID house = LLUUID::generateNewID(); const LLUUID shed = LLUUID::generateNewID(); @@ -803,32 +910,41 @@ namespace tut pane->checkedScript(closed, "hinge.lsl", false, { row }, "House"); pane->showObject(house); std::string listed = shown(); - ensure("the house's open scripts: " + listed, listed.find("the door's") != std::string::npos && listed.find("the lamp's") != std::string::npos); - ensure("and the one it read: " + listed, listed.find("the hinge's") != std::string::npos); - ensure("not the shed's: " + listed, listed.find("the bench's") == std::string::npos); - ensure_equals("counted as the house's", pane->held(), 3); + { + INFO("the house's open scripts: " + listed); + REQUIRE(listed.find("the door's") != std::string::npos); + REQUIRE(listed.find("the lamp's") != std::string::npos); + } + UNSCOPED_INFO("and the one it read: " + listed); + REQUIRE(listed.find("the hinge's") != std::string::npos); + UNSCOPED_INFO("not the shed's: " + listed); + REQUIRE(listed.find("the bench's") == std::string::npos); + UNSCOPED_INFO("counted as the house's"); + CHECK(pane->held() == 3); // In front or not, the shed's script is not the house's. services.front = 2; pane->fill(&bench); - ensure("the shed's in front, still not listed", shown().find("the bench's") == std::string::npos); + UNSCOPED_INFO("the shed's in front, still not listed"); + REQUIRE(shown().find("the bench's") == std::string::npos); // Every open script's lists them all; and it is what is kept. pane->showEveryScript(); listed = shown(); - ensure("every open one's: " + listed, listed.find("the bench's") != std::string::npos && listed.find("the door's") != std::string::npos); + { + INFO("every open one's: " + listed); + REQUIRE(listed.find("the bench's") != std::string::npos); + REQUIRE(listed.find("the door's") != std::string::npos); + } pane->showObject(house); LLSD state; pane->saveState(state); - ensure_equals("one object's is not kept", state["problem_scope"].asString(), std::string("all")); + UNSCOPED_INFO("one object's is not kept"); + CHECK(state["problem_scope"].asString() == std::string("all")); } - template <> - template <> - void alscriptproblemspane_object::test<14>() + TEST_CASE_METHOD(alscriptproblemspane_data, "ALScriptProblemsPane: only LSL leftovers in a ported script are listed and counted as from LSL", "[ALScriptProblemsPane]") { - set_test_name("what a script moved from LSL is left with -- a converter's note, an LSL habit's lint, a call to llcompat -- listed as " - "from LSL, counted, and the origin that lists them alone; not a performance lint, nor ll's own deprecation"); make(); Doc& d = doc("door"); d.language.lua = true; @@ -856,23 +972,26 @@ namespace tut { if (row.migration) { - ensure_equals("said to be from LSL", row.origin, services.words("OriginMigration")); + UNSCOPED_INFO("said to be from LSL"); + REQUIRE(row.origin == services.words("OriginMigration")); from_lsl += row.message.substr(0, 4) + ";"; } } - ensure_equals("the three", from_lsl, std::string("a no;a nu;llco;")); + UNSCOPED_INFO("the three"); + CHECK(from_lsl == std::string("a no;a nu;llco;")); d.setShown(made.rows); - ensure_equals("counted", d.shownMigration, 3); + CHECK(d.shownMigration == 3); pane->showOrigin("OriginMigration"); LLComboBox* origins = window.find("problems_origin"); - ensure("listed alone", origins && origins->getValue().asString() == services.words("OriginMigration")); + { + INFO("listed alone"); + REQUIRE(origins); + CHECK(origins->getValue().asString() == services.words("OriginMigration")); + } } - template <> - template <> - void alscriptproblemspane_object::test<15>() + TEST_CASE_METHOD(alscriptproblemspane_data, "ALScriptProblemsPane: only an LSL leftover's menu offers and makes the from-LSL fix", "[ALScriptProblemsPane]") { - set_test_name("the menu of a problem left from LSL offers what is left from LSL fixed, and makes it; another problem's does not"); ALScriptProblemsPane& out = make(); Doc& d = doc("door"); d.language.lua = true; @@ -886,53 +1005,70 @@ namespace tut gather(d); out.fill(&d); choose("shadowed [LocalShadow]"); - ensure("not of another", !out.fixShown("migration")); + UNSCOPED_INFO("not of another"); + CHECK_FALSE(out.fixShown("migration")); choose("reads nothing at 0 [SlZeroIndex]"); - ensure("offered", out.fixShown("migration")); + CHECK(out.fixShown("migration")); out.act("fix_migration"); - ensure("asked", !studio.kinds.empty() && studio.kinds.back() == "door:from LSL"); + REQUIRE_FALSE(studio.kinds.empty()); + CHECK(studio.kinds.back() == "door:from LSL"); } - template <> - template <> - void alscriptproblemspane_object::test<16>() + TEST_CASE_METHOD(alscriptproblemspane_data, "ALScriptProblemsPane: a problem naming no line is listed and chosen at none and marked nowhere", "[ALScriptProblemsPane]") { - set_test_name("what names no line -- a compiler's line of no form it has, a run-time error of no place -- is listed at none, marked " - "nowhere, and chosen at none, and gone to as the first error only where no error names a line; one that names a line, at it"); ALScriptProblemsPane& out = make(); Doc& d = doc("door"); d.problems.push_back({ -1, 0, false, std::string(), "ERROR", "the compiler's, nowhere" }); d.problems.push_back({ 3, 0, false, std::string(), "WARN", "the compiler's, at 3" }); d.runtime.push_back({ -1, -1, std::string(), "Stack-Heap Collision", 1 }); const ALScriptProblemsPane::Made made = gather(d); - ensure_equals("all three", made.rows.size(), size_t(3)); - ensure("at none, first", made.rows[0].line == -1 && made.rows[1].line == -1 && made.rows[2].line == 3); - ensure("a warning said WARN", made.rows[2].level == Doc::Level::Warning); - ensure("marked at its line alone", made.marks.size() == 1 && made.marks[0].first == 3); - ensure("waved at its line alone", made.decorations.size() == 1 && made.decorations[0].range.begin.line == 3); + REQUIRE(made.rows.size() == size_t(3)); + { + INFO("at none, first"); + CHECK(made.rows[0].line == -1); + CHECK(made.rows[1].line == -1); + CHECK(made.rows[2].line == 3); + } + UNSCOPED_INFO("a warning said WARN"); + CHECK(made.rows[2].level == Doc::Level::Warning); + { + INFO("marked at its line alone"); + REQUIRE(made.marks.size() == 1); + CHECK(made.marks[0].first == 3); + } + { + INFO("waved at its line alone"); + REQUIRE(made.decorations.size() == 1); + CHECK(made.decorations[0].range.begin.line == 3); + } out.fill(&d); list()->updateSort(); for (const LLScrollListItem* item : list()->getAllData()) { const std::string message = item->getColumn(1)->getValue().asString(); const std::string where = item->getColumn(3)->getValue().asString(); - ensure("where: " + message + " " + where, message == "the compiler's, at 3" ? where == "4" : where.empty()); + CAPTURE(message, where); + REQUIRE((message == "the compiler's, at 3" ? where == "4" : where.empty())); } choose("the compiler's, nowhere"); out.choose(false); - ensure_equals("chosen at none", studio.chosen.back().line, -1); + UNSCOPED_INFO("chosen at none"); + CHECK(studio.chosen.back().line == -1); choose("the compiler's, at 3"); out.choose(false); - ensure_equals("chosen at its line", studio.chosen.back().line, 3); + UNSCOPED_INFO("chosen at its line"); + CHECK(studio.chosen.back().line == 3); // The first error gone to: one at none, listed first, only where no // error names a line. out.selectFirstError(false); - ensure_equals("one at none, where no error is at a line", studio.chosen.back().line, -1); + UNSCOPED_INFO("one at none, where no error is at a line"); + CHECK(studio.chosen.back().line == -1); d.problems.push_back({ 5, 0, false, std::string(), "ERROR", "the compiler's, at 5" }); gather(d); out.fill(&d); out.selectFirstError(false); - ensure_equals("one at a line, past those at none", studio.chosen.back().line, 5); + UNSCOPED_INFO("one at a line, past those at none"); + CHECK(studio.chosen.back().line == 5); } } diff --git a/indra/newview/tests/alscriptrecompile_test.cpp b/indra/newview/tests/alscriptrecompile_test.cpp index b2e39c738d..fe67d2fabf 100644 --- a/indra/newview/tests/alscriptrecompile_test.cpp +++ b/indra/newview/tests/alscriptrecompile_test.cpp @@ -33,7 +33,7 @@ #include "alscriptstudio_fixture.h" -#include "../test/lltut.h" +#include #include #include @@ -122,7 +122,7 @@ namespace } } -namespace tut +namespace { struct alscriptrecompile_data { @@ -148,76 +148,106 @@ namespace tut [&](const auto& one) { return one.text.find(word) == 0 && one.failure == failure; }); } }; - typedef test_group alscriptrecompile_group; - typedef alscriptrecompile_group::object alscriptrecompile_object; - alscriptrecompile_group alscriptrecompile_instance("alscriptrecompile"); - template<> template<> - void alscriptrecompile_object::test<1>() + // SLua goes up as SLua, and what is known of whether each runs is given. + TEST_CASE_METHOD(alscriptrecompile_data, "alscriptrecompile: a prim's scripts and one chosen go up a few at a time each once", "[alscriptrecompile]") { - set_test_name("a prim's scripts and a script chosen go up a few at a time, each once, for the target; SLua as SLua; what is known of running given"); house(); window.runs = { { id(11), false } }; run.recompile({ one(10, "A") }, { { prim, "House" } }, "mono"); - ensure("listed", window.listedFor.size() == 1 && window.listedFor[0].front().first == prim); - ensure("running", run.running()); - ensure_equals("four at once", window.asked.size(), size_t(Recompile::AT_ONCE)); - ensure("A once, chosen and in its prim", window.asked[0].ref.item == id(10) && window.asked[1].ref.item == id(11)); - ensure_equals("for the target", window.asked[0].target, std::string("mono")); - ensure_equals("the SLua one as it compiles", window.asked[2].target, std::string("auto")); - ensure("B known stopped, stays so", window.asked[1].running == false); - ensure("A not known: the save asks", !window.asked[0].running.has_value()); + REQUIRE(window.listedFor.size() == 1); + CHECK(window.listedFor[0].front().first == prim); + CHECK(run.running()); + REQUIRE(window.asked.size() == size_t(Recompile::AT_ONCE)); + { + INFO("A once, chosen and in its prim"); + CHECK(window.asked[0].ref.item == id(10)); + CHECK(window.asked[1].ref.item == id(11)); + } + CHECK(window.asked[0].target == std::string("mono")); + UNSCOPED_INFO("the SLua one as it compiles"); + CHECK(window.asked[2].target == std::string("auto")); + UNSCOPED_INFO("B known stopped, stays so"); + CHECK(window.asked[1].running == false); + UNSCOPED_INFO("A not known: the save asks"); + CHECK_FALSE(window.asked[0].running.has_value()); for (size_t i = 0; i < 4; ++i) { window.asked[i].told(compiled()); } - ensure_equals("the rest begun", window.asked.size(), size_t(6)); + UNSCOPED_INFO("the rest begun"); + REQUIRE(window.asked.size() == size_t(6)); window.asked[4].told(compiled()); - ensure("not done yet", window.done.empty()); + UNSCOPED_INFO("not done yet"); + CHECK(window.done.empty()); window.asked[5].told(compiled()); - ensure_equals("done once", window.done.size(), size_t(1)); - ensure("all compiled", window.done[0].compiled == 6 && window.done[0].failed == 0 && window.done[0].target == "mono"); - ensure("no longer running", !run.running()); - ensure_equals("each said", window.scripts.size(), size_t(6)); + REQUIRE(window.done.size() == size_t(1)); + CHECK(window.done[0].compiled == 6); + CHECK(window.done[0].failed == 0); + CHECK(window.done[0].target == "mono"); + CHECK_FALSE(run.running()); + UNSCOPED_INFO("each said"); + CHECK(window.scripts.size() == size_t(6)); } - template<> template<> - void alscriptrecompile_object::test<2>() + // Changes not saved are said too, and a closed script's result carries + // its rows. + TEST_CASE_METHOD(alscriptrecompile_data, "alscriptrecompile: a script being saved is skipped and each result is said in Output", "[alscriptrecompile]") { - set_test_name("a save on its way is left alone and said, changes not saved are said, and each result said in Output with a closed one's rows"); house(); window.sending = { id(11) }; window.changed = { id(12) }; window.open = { id(14) }; run.recompile({}, { { prim, "House" } }, "auto"); - ensure("B left alone", std::none_of(window.asked.begin(), window.asked.end(), [](const auto& a) { return a.ref.item == id(11); })); - ensure("and said", said("RecompileSkippedSaving", true)); - ensure("C's unsaved changes said", said("RecompileUnsaved", true)); - ensure_equals("four begun past B", window.asked.size(), size_t(4)); + UNSCOPED_INFO("B left alone"); + CHECK(std::none_of(window.asked.begin(), window.asked.end(), [](const auto& a) { return a.ref.item == id(11); })); + UNSCOPED_INFO("B's save on its way said"); + CHECK(said("RecompileSkippedSaving", true)); + UNSCOPED_INFO("C's unsaved changes said"); + CHECK(said("RecompileUnsaved", true)); + UNSCOPED_INFO("four begun past B"); + REQUIRE(window.asked.size() == size_t(4)); window.asked[0].told(compiled()); // A window.asked[1].told(failed(2, "syntax error")); // C window.asked[2].told(notSent("no such object")); // D window.asked[3].told(failed(0, "wrong")); // E, open - ensure("the last begun", window.asked.size() == 5 && window.asked[4].ref.item == id(15)); + { + INFO("the last begun"); + REQUIRE(window.asked.size() == 5); + CHECK(window.asked[4].ref.item == id(15)); + } window.asked[4].told(compiled()); // F - ensure("compiled said", said("Compiled", false)); - ensure("a failure said", said("CompileFailed", true)); - ensure("one not sent said with why", said("SaveFailed", true)); + CHECK(said("Compiled", false)); + CHECK(said("CompileFailed", true)); + UNSCOPED_INFO("one not sent said with why"); + CHECK(said("SaveFailed", true)); const Recompile::Script& c = window.scripts[1]; - ensure("C's rows, by the compiler", c.one.name == "C" && c.rows.size() == 1 && c.rows[0].line == 2 && c.rows[0].level == ALScriptStudioDoc::Level::Error); - ensure("E is open: its tab says it", window.scripts[3].open && window.scripts[3].rows.empty()); - ensure("D's why", window.scripts[2].error == "no such object"); + { + INFO("C's rows, by the compiler"); + CHECK(c.one.name == "C"); + REQUIRE(c.rows.size() == 1); + CHECK(c.rows[0].line == 2); + CHECK(c.rows[0].level == ALScriptStudioDoc::Level::Error); + } + { + INFO("E is open: its tab says it"); + CHECK(window.scripts[3].open); + CHECK(window.scripts[3].rows.empty()); + } + UNSCOPED_INFO("D's why"); + CHECK(window.scripts[2].error == "no such object"); const Recompile::Done& done = window.done.at(0); - ensure("counted", done.compiled == 2 && done.failed == 2 && done.notSent == 1 && done.skipped == 1); + CHECK(done.compiled == 2); + CHECK(done.failed == 2); + CHECK(done.notSent == 1); + CHECK(done.skipped == 1); } - template<> template<> - void alscriptrecompile_object::test<3>() + TEST_CASE_METHOD(alscriptrecompile_data, "alscriptrecompile: rowsOf reads a compiler's lines back to the source and its includes", "[alscriptrecompile]") { - set_test_name("what the compiler said is read back through the expansion that went up, under the envelope's lines, to the source and its includes"); ALPreprocessor::Options options; options.fileName = "main.lsl"; options.resolve = [](const ALPreprocessor::Ask& ask, ALPreprocessor::Include& out) { @@ -226,7 +256,7 @@ namespace tut return ALPreprocessor::Found::Yes; }; const ALPreprocessor::Result expanded = ALPreprocessor::run("#include \"lib.lsl\"\ndefault\n{\nstate_entry() { x; }\n}\n", options); - ensure("expanded", expanded.problems.empty()); + REQUIRE(expanded.problems.empty()); // The lines of the expansion that hold each. S32 entry = -1, second = -1, line = 0; for (size_t at = 0, next; at < expanded.text.size(); at = next + 1, ++line) @@ -240,7 +270,11 @@ namespace tut break; } } - ensure("found in the expansion", entry >= 0 && second >= 0); + { + INFO("found in the expansion"); + CHECK(entry >= 0); + CHECK(second >= 0); + } Result result = failed(entry + 7, "undefined x"); result.codeLine = 7; @@ -248,41 +282,69 @@ namespace tut result.diagnostics.push_back(failed(second + 7, "b again").diagnostics[0]); result.diagnostics.push_back(failed(5000, "somewhere made").diagnostics[0]); const std::vector rows = Recompile::rowsOf(result, "Compiler", "made"); - ensure_equals("three", rows.size(), size_t(3)); - ensure("the script's own line", rows[0].line == 3 && rows[0].file.empty() && rows[0].origin == "Compiler"); - ensure("the include's, by its name", rows[1].line == 1 && rows[1].fileName == "lib.lsl"); - ensure("none: made by the preprocessor", rows[2].file == ALScriptStudioDoc::GENERATED && rows[2].fileName == "made"); + REQUIRE(rows.size() == size_t(3)); + { + INFO("the script's own line"); + CHECK(rows[0].line == 3); + CHECK(rows[0].file.empty()); + CHECK(rows[0].origin == "Compiler"); + } + { + INFO("the include's, by its name"); + CHECK(rows[1].line == 1); + CHECK(rows[1].fileName == "lib.lsl"); + } + { + INFO("none: made by the preprocessor"); + CHECK(rows[2].file == ALScriptStudioDoc::GENERATED); + CHECK(rows[2].fileName == "made"); + } // Nothing expanded: as the compiler said it. const std::vector plain = Recompile::rowsOf(failed(4, "as is"), "Compiler", "made"); - ensure("as said", plain.size() == 1 && plain[0].line == 4 && plain[0].file.empty()); + { + INFO("as the compiler said it"); + REQUIRE(plain.size() == 1); + CHECK(plain[0].line == 4); + CHECK(plain[0].file.empty()); + } } - template<> template<> - void alscriptrecompile_object::test<4>() + TEST_CASE_METHOD(alscriptrecompile_data, "alscriptrecompile: a recompile begun again drops the last and an empty one is done at once", "[alscriptrecompile]") { - set_test_name("a recompile begun again lets the last go; one with nothing to do is done at once"); house(); run.recompile({}, { { prim, "House" } }, "auto"); const size_t first = window.asked.size(); window.listing.scripts.resize(1); run.recompile({}, { { prim, "House" } }, "lsl2"); - ensure_equals("the new one begun", window.asked.size(), first + 1); + UNSCOPED_INFO("the new one begun"); + REQUIRE(window.asked.size() == first + 1); window.asked[0].told(compiled()); - ensure("the old one's answer passed over", window.scripts.empty() && window.done.empty()); + { + INFO("the old one's answer passed over"); + CHECK(window.scripts.empty()); + CHECK(window.done.empty()); + } window.asked[first].told(compiled()); - ensure("the new one done", window.done.size() == 1 && window.done[0].target == "lsl2" && window.done[0].compiled == 1); + { + INFO("the new one done"); + REQUIRE(window.done.size() == 1); + CHECK(window.done[0].target == "lsl2"); + CHECK(window.done[0].compiled == 1); + } window.listing.scripts.clear(); run.recompile({}, {}, "auto"); - ensure("nothing: done at once", window.done.size() == 2 && window.done[1].compiled == 0 && !run.running()); + { + INFO("nothing: done at once"); + REQUIRE(window.done.size() == 2); + CHECK(window.done[1].compiled == 0); + CHECK_FALSE(run.running()); + } } - template<> template<> - void alscriptrecompile_object::test<5>() + TEST_CASE_METHOD(alscriptrecompile_data, "alscriptrecompile: places named in a compiler's message are read back as its line is", "[alscriptrecompile]") { - set_test_name("the places a compiler's words name are read back as its line is, the script's own or an include's by name; one " - "that names no line is at none"); ALPreprocessor::Options options; options.fileName = "main.lsl"; options.resolve = [](const ALPreprocessor::Ask& ask, ALPreprocessor::Include& out) { @@ -291,7 +353,7 @@ namespace tut return ALPreprocessor::Found::Yes; }; const ALPreprocessor::Result expanded = ALPreprocessor::run("#include \"lib.lsl\"\ndefault\n{\nstate_entry() { x; }\n}\n", options); - ensure("expanded", expanded.problems.empty()); + REQUIRE(expanded.problems.empty()); S32 entry = -1, second = -1, line = 0; for (size_t at = 0, next; at < expanded.text.size(); at = next + 1, ++line) { @@ -304,7 +366,11 @@ namespace tut break; } } - ensure("found in the expansion", entry >= 0 && second >= 0); + { + INFO("found in the expansion"); + CHECK(entry >= 0); + CHECK(second >= 0); + } // As Tailslide says it, one-based, under the envelope's seven lines. LLSD said; @@ -326,15 +392,32 @@ namespace tut result.diagnostics.push_back(one); } const std::vector rows = Recompile::rowsOf(result, "Compiler", "made"); - ensure_equals("three", rows.size(), size_t(3)); - ensure("the line, the source's", rows[0].line == 3 && rows[0].level == ALScriptStudioDoc::Level::Warning); - ensure_equals("the place named, the source's", rows[0].message, std::string("x shadows previous declaration at (4, 1)")); - ensure_equals("an include's, by its name; one made, as said", rows[1].message, + REQUIRE(rows.size() == size_t(3)); + { + INFO("the line, the source's"); + CHECK(rows[0].line == 3); + CHECK(rows[0].level == ALScriptStudioDoc::Level::Warning); + } + UNSCOPED_INFO("the place named, the source's"); + CHECK(rows[0].message == std::string("x shadows previous declaration at (4, 1)")); + UNSCOPED_INFO("an include's, by its name; one made, as said"); + CHECK(rows[1].message == std::string("b previously declared at (2, 9) in lib.lsl. Again at (5000, 1).")); - ensure("no line named, none", rows[2].line == -1 && rows[2].file.empty() && rows[2].message == "Math Error"); + { + INFO("no line named, none"); + CHECK(rows[2].line == -1); + CHECK(rows[2].file.empty()); + CHECK(rows[2].message == "Math Error"); + } // A tab's, the same. const std::vector tab = ALScriptStudioDoc::compiledOf(result.diagnostics, result.sourceMap.get(), 7); - ensure("a tab's", tab.size() == 3 && tab[0].line == 3 && tab[0].message == rows[0].message && tab[2].line == -1); + { + INFO("a tab's"); + REQUIRE(tab.size() == 3); + CHECK(tab[0].line == 3); + CHECK(tab[0].message == rows[0].message); + CHECK(tab[2].line == -1); + } } } diff --git a/indra/newview/tests/alscriptreferencespane_test.cpp b/indra/newview/tests/alscriptreferencespane_test.cpp index db9f569b9b..46e7f84b33 100644 --- a/indra/newview/tests/alscriptreferencespane_test.cpp +++ b/indra/newview/tests/alscriptreferencespane_test.cpp @@ -35,7 +35,7 @@ #include "llscrolllistitem.h" #include "lltextbox.h" -#include "../test/lltut.h" +#include namespace { @@ -90,7 +90,7 @@ namespace } } -namespace tut +namespace { struct alscriptreferencespane_data { @@ -103,7 +103,7 @@ namespace tut ALScriptReferencesPane* found = window.find("references_tab"); if (!found) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } // In sight, its tab the one chosen, as when it is worked in. window.find("bottom_tabs")->selectTabByName("references_tab"); @@ -147,43 +147,43 @@ namespace tut } }; - typedef test_group alscriptreferencespane_group; - typedef alscriptreferencespane_group::object alscriptreferencespane_object; - alscriptreferencespane_group alscriptreferencespane_instance("alscriptreferencespane"); - - template<> template<> - void alscriptreferencespane_object::test<1>() + TEST_CASE_METHOD(alscriptreferencespane_data, "alscriptreferencespane: show lists places and marks the declaration and forget clears them", "[alscriptreferencespane]") { - set_test_name("what was found listed under what was looked up, the declaration marked; nothing, with the question it answers"); ALScriptReferencesPane* refs = pane(); refs->show(found()); - ensure_equals("the head", head(), std::string("3 occurrences of count in 2 scripts.")); - ensure("the window told", window.pane().counts == 1); - ensure_equals("each place", lines(), joined(Names{ "A 1:9", "A 2:5", "B 1:9" })); + CHECK(head() == std::string("3 occurrences of count in 2 scripts.")); + UNSCOPED_INFO("the window told"); + CHECK(window.pane().counts == 1); + CHECK(lines() == joined(Names{ "A 1:9", "A 2:5", "B 1:9" })); const std::vector rows = refs->list()->getAllData(); - ensure_equals("the declaration", cell(rows[0], 2), std::string("declaration")); - ensure_equals("in the script it is declared in only", cell(rows[2], 2), std::string()); - ensure_equals("its text", cell(rows[1], 3), std::string("x = count;")); - ensure_equals("kept", refs->found().places.size(), size_t(3)); + CHECK(cell(rows[0], 2) == std::string("declaration")); + UNSCOPED_INFO("in the script it is declared in only"); + CHECK(cell(rows[2], 2) == std::string()); + UNSCOPED_INFO("its text"); + CHECK(cell(rows[1], 3) == std::string("x = count;")); + UNSCOPED_INFO("kept"); + CHECK(refs->found().places.size() == size_t(3)); Found twice = found(); twice.places.push_back(place("", 0, 20, "integer count; count")); refs->show(twice); - ensure("on the declaration's line, not it", cell(refs->list()->getAllData()[3], 2).empty()); + UNSCOPED_INFO("on the declaration's line, not it"); + CHECK(cell(refs->list()->getAllData()[3], 2).empty()); Found in_b = found(); in_b.home = B; refs->show(in_b); - ensure("declared in B: A's first line not it", cell(refs->list()->getAllData()[0], 2).empty()); + UNSCOPED_INFO("declared in B: A's first line not it"); + CHECK(cell(refs->list()->getAllData()[0], 2).empty()); refs->forget(); - ensure_equals("nothing", head(), std::string("Nothing to show yet. Put the caret on a name and choose Find References.")); - ensure("no rows", refs->list()->getItemCount() == 0 && refs->found().from.empty()); - ensure("told each time", window.pane().counts == 4); + CHECK(head() == std::string("Nothing to show yet. Put the caret on a name and choose Find References.")); + CHECK(refs->list()->getItemCount() == 0); + CHECK(refs->found().from.empty()); + UNSCOPED_INFO("told each time"); + CHECK(window.pane().counts == 4); } - template<> template<> - void alscriptreferencespane_object::test<2>() + TEST_CASE_METHOD(alscriptreferencespane_data, "alscriptreferencespane: places slide with their script's edits and go where the name is edited", "[alscriptreferencespane]") { - set_test_name("places slid with the edits to what they are in, listed again once the frame is drawn; gone where the name was edited"); ALScriptReferencesPane* refs = pane(); Doc& a = tab("a", "integer count;\nx = count;\n"); Doc& b = tab("b", "foo() { count; }\n"); @@ -200,69 +200,87 @@ namespace tut c.editor->replaceAll({ { ALTextRange(ALTextPos(0, 0), ALTextPos(0, 0)), "// one\n" } }); a.editor->replaceAll({ { ALTextRange(ALTextPos(0, 0), ALTextPos(0, 0)), "// one\n" } }); - ensure_equals("not listed again yet", lines(), joined(Names{ "A 1:9", "A 2:5", "B 1:9" })); + UNSCOPED_INFO("not listed again yet"); + CHECK(lines() == joined(Names{ "A 1:9", "A 2:5", "B 1:9" })); refs->pump(); - ensure_equals("A's slid, B's not", lines(), joined(Names{ "A 2:9", "A 3:5", "B 1:9" })); - ensure("the row chosen kept", refs->list()->getFirstSelected() && refs->list()->getFirstSelected()->getValue().asInteger() == 1); - ensure_equals("the declaration too", refs->found().definition.line, 1); + UNSCOPED_INFO("A's slid, B's not"); + CHECK(lines() == joined(Names{ "A 2:9", "A 3:5", "B 1:9" })); + { + INFO("the row chosen kept"); + REQUIRE(refs->list()->getFirstSelected()); + CHECK(refs->list()->getFirstSelected()->getValue().asInteger() == 1); + } + UNSCOPED_INFO("the declaration too"); + CHECK(refs->found().definition.line == 1); b.editor->replaceAll({ { ALTextRange(ALTextPos(0, 0), ALTextPos(0, 0)), " " } }); refs->pump(); - ensure_equals("B's by its path", lines(), joined(Names{ "A 2:9", "A 3:5", "B 1:11" })); - ensure_equals("its line's words", refs->found().places[2].text, std::string("foo() { count; }")); + UNSCOPED_INFO("B's by its path"); + CHECK(lines() == joined(Names{ "A 2:9", "A 3:5", "B 1:11" })); + UNSCOPED_INFO("its line's words"); + CHECK(refs->found().places[2].text == std::string("foo() { count; }")); a.editor->replaceAll({ { ALTextRange(ALTextPos(1, 8), ALTextPos(1, 13)), "total" } }); refs->pump(); - ensure_equals("the declaration edited: gone", lines(), joined(Names{ "A 3:5", "B 1:11" })); - ensure("not declared any more", !refs->found().hasDefinition); + UNSCOPED_INFO("the declaration edited: gone"); + CHECK(lines() == joined(Names{ "A 3:5", "B 1:11" })); + UNSCOPED_INFO("not declared any more"); + CHECK_FALSE(refs->found().hasDefinition); a.editor->replaceAll({ { ALTextRange(ALTextPos(2, 0), ALTextPos(2, 1)), "yy" } }); refs->pump(); - ensure_equals("a place on the line edited", lines(), joined(Names{ "A 3:6", "B 1:11" })); - ensure_equals("its words again", refs->found().places[0].text, std::string("yy = count;")); + UNSCOPED_INFO("a place on the line edited"); + CHECK(lines() == joined(Names{ "A 3:6", "B 1:11" })); + UNSCOPED_INFO("its words again"); + CHECK(refs->found().places[0].text == std::string("yy = count;")); const S32 counts = window.pane().counts; refs->pump(); - ensure_equals("not listed again for nothing", window.pane().counts, counts); + UNSCOPED_INFO("not listed again for nothing"); + CHECK(window.pane().counts == counts); a.editor->replaceAll({ { ALTextRange(ALTextPos(0, 0), ALTextPos(0, 0)), "\n" } }); a.editor->replaceAll({ { ALTextRange(ALTextPos(4, 0), ALTextPos(4, 0)), "z" } }); refs->pump(); - ensure_equals("moved, not on a line edited; then an edit that moves nothing", lines(), joined(Names{ "A 4:6", "B 1:11" })); + UNSCOPED_INFO("moved, not on a line edited; then an edit that moves nothing"); + CHECK(lines() == joined(Names{ "A 4:6", "B 1:11" })); } - template<> template<> - void alscriptreferencespane_object::test<3>() + TEST_CASE_METHOD(alscriptreferencespane_data, "alscriptreferencespane: choosing a row goes to its place and follows its tab's new id", "[alscriptreferencespane]") { - set_test_name("a row chosen gone to, walked or taken to the editor; the tab it was looked up from followed to its new id"); ALScriptReferencesPane* refs = pane(); refs->show(found()); refs->choose(false); - ensure("nothing chosen, nothing gone to", window.pane().chosen.empty()); + UNSCOPED_INFO("nothing chosen, nothing gone to"); + CHECK(window.pane().chosen.empty()); // A row is known by its place's number, counted from one. refs->list()->selectByValue(LLSD(3)); refs->list()->onCommit(); refs->choose(true); - ensure_equals("gone to", joined(window.pane().chosen), joined(Names{ "a|" + B + "|0", "a|" + B + "|0 editor" })); + UNSCOPED_INFO("gone to"); + CHECK(joined(window.pane().chosen) == joined(Names{ "a|" + B + "|0", "a|" + B + "|0 editor" })); refs->rekey("elsewhere", "z"); - ensure_equals("another's id", refs->found().from, std::string("a")); + UNSCOPED_INFO("another's id"); + CHECK(refs->found().from == std::string("a")); refs->rekey("a", "a2"); refs->list()->selectByValue(LLSD(1)); refs->choose(false); - ensure_equals("followed", window.pane().chosen.back(), std::string("a2||0")); + UNSCOPED_INFO("followed"); + CHECK(window.pane().chosen.back() == std::string("a2||0")); // The list's keys: return goes to the place chosen, to type there, // escape back to the script; and the rows copy. - ensure("return", refs->list()->handleKeyHere(KEY_RETURN, MASK_NONE) && window.pane().chosen.back() == "a2||0 editor"); + CHECK(refs->list()->handleKeyHere(KEY_RETURN, MASK_NONE)); + CHECK(window.pane().chosen.back() == "a2||0 editor"); const size_t gone = window.pane().chosen.size(); - ensure("escape", refs->list()->handleKeyHere(KEY_ESCAPE, MASK_NONE) && window.services().reveals.back() - && window.pane().chosen.size() == gone); - ensure("copied", refs->list()->handleKeyHere('C', MASK_CONTROL)); + CHECK(refs->list()->handleKeyHere(KEY_ESCAPE, MASK_NONE)); + CHECK(window.services().reveals.back()); + CHECK(window.pane().chosen.size() == gone); + UNSCOPED_INFO("copied"); + CHECK(refs->list()->handleKeyHere('C', MASK_CONTROL)); } - template<> template<> - void alscriptreferencespane_object::test<4>() + TEST_CASE_METHOD(alscriptreferencespane_data, "alscriptreferencespane: rows sort by a column and then by script and place", "[alscriptreferencespane]") { - set_test_name("sorted by a column, then by script and place: by text, by script the other way, the declaration first"); ALScriptReferencesPane* refs = pane(); Found many = found(); many.places.push_back(place("", 2, 0, "count = 0;")); @@ -270,24 +288,25 @@ namespace tut ALPaneList* list = refs->list(); list->sortByColumn("text", true); list->updateSort(); - ensure_equals("by text", lines(), joined(Names{ "A 3:1", "B 1:9", "A 1:9", "A 2:5" })); + CHECK(lines() == joined(Names{ "A 3:1", "B 1:9", "A 1:9", "A 2:5" })); list->sortByColumn("where", false); list->updateSort(); - ensure_equals("by script, then place, the other way", lines(), joined(Names{ "B 1:9", "A 3:1", "A 2:5", "A 1:9" })); + UNSCOPED_INFO("by script, then place, the other way"); + CHECK(lines() == joined(Names{ "B 1:9", "A 3:1", "A 2:5", "A 1:9" })); list->sortByColumn("role", true); list->updateSort(); - ensure_equals("the declaration first, then by script", lines(), joined(Names{ "A 1:9", "A 2:5", "A 3:1", "B 1:9" })); + UNSCOPED_INFO("the declaration first, then by script"); + CHECK(lines() == joined(Names{ "A 1:9", "A 2:5", "A 3:1", "B 1:9" })); many.home = B; refs->show(many); list->sortByColumn("role", true); list->updateSort(); - ensure_equals("the declaration first, then by script", lines(), joined(Names{ "B 1:9", "A 1:9", "A 2:5", "A 3:1" })); + UNSCOPED_INFO("the declaration first, then by script"); + CHECK(lines() == joined(Names{ "B 1:9", "A 1:9", "A 2:5", "A 3:1" })); } - template<> template<> - void alscriptreferencespane_object::test<5>() + TEST_CASE_METHOD(alscriptreferencespane_data, "alscriptreferencespane: a row picked before relisting after a place is removed still goes to its place", "[alscriptreferencespane]") { - set_test_name("a row picked between an edit that took a place away and the listing again is still its own place"); ALScriptReferencesPane* refs = pane(); Doc& a = tab("a", "integer count;\nx = count;\n"); refs->show(found()); @@ -295,22 +314,24 @@ namespace tut [refs, &a](const ALTextDocument::Edit& edit) { refs->slide(a, "object:1:a", edit); })); refs->list()->selectFirstItem(); refs->list()->selectNextItem(false); - ensure_equals("A's second chosen", cell(refs->list()->getFirstSelected(), 1), std::string("2:5")); + UNSCOPED_INFO("A's second chosen"); + CHECK(cell(refs->list()->getFirstSelected(), 1) == std::string("2:5")); // The declaration's name edited: the first place goes, not listed // again until the frame is drawn. a.editor->replaceAll({ { ALTextRange(ALTextPos(0, 8), ALTextPos(0, 13)), "total" } }); - ensure_equals("one place fewer", refs->found().places.size(), size_t(2)); + UNSCOPED_INFO("one place fewer"); + CHECK(refs->found().places.size() == size_t(2)); window.pane().chosen.clear(); refs->choose(false); - ensure_equals("A's second, not what took its index", joined(window.pane().chosen), std::string("a||1")); + UNSCOPED_INFO("A's second, not what took its index"); + CHECK(joined(window.pane().chosen) == std::string("a||1")); refs->pump(); - ensure_equals("listed again, the row still chosen", cell(refs->list()->getFirstSelected(), 1), std::string("2:5")); + UNSCOPED_INFO("listed again, the row still chosen"); + CHECK(cell(refs->list()->getFirstSelected(), 1) == std::string("2:5")); } - template<> template<> - void alscriptreferencespane_object::test<6>() + TEST_CASE_METHOD(alscriptreferencespane_data, "alscriptreferencespane: a rename preview checks each place and renames only those kept", "[alscriptreferencespane]") { - set_test_name("a rename previewed: a box by each place and its line as renamed, Rename with the places kept, Space and a box leaving one out, Cancel listing them as found"); ALScriptReferencesPane* refs = pane(); std::vector made; bool applied = false; @@ -318,33 +339,60 @@ namespace tut made = kept; applied = true; }); - ensure("previewing", refs->previewing()); - ensure_equals("what it will do over them", head(), std::string("Rename 3 places")); + CHECK(refs->previewing()); + UNSCOPED_INFO("what it will do over them"); + CHECK(head() == std::string("Rename 3 places")); LLButton* rename = window.find("references_rename"); - ensure("its buttons shown", rename && rename->getVisible() && window.find("references_cancel")->getVisible()); + { + INFO("its buttons shown"); + REQUIRE(rename); + CHECK(rename->getVisible()); + CHECK(window.find("references_cancel")->getVisible()); + } const std::vector rows = refs->list()->getAllData(); - ensure("each checked", rows.size() == 3 && std::all_of(rows.begin(), rows.end(), [](const LLScrollListItem* row) { + { + INFO("each checked"); + REQUIRE(rows.size() == 3); + CHECK(std::all_of(rows.begin(), rows.end(), [](const LLScrollListItem* row) { return row->getColumn(2)->getValue().asBoolean(); })); - ensure_equals("the line as it would read", cell(rows[1], 3), std::string("x = total;")); + } + UNSCOPED_INFO("the line as it would read"); + CHECK(cell(rows[1], 3) == std::string("x = total;")); // Left out by Space on the row chosen: its box clear, its line as it is. refs->list()->selectNthItem(1); refs->list()->handleUnicodeCharHere(' '); - ensure("left out", !refs->list()->getAllData()[1]->getColumn(2)->getValue().asBoolean()); - ensure_equals("its line as it is", cell(refs->list()->getAllData()[1], 3), std::string("x = count;")); + UNSCOPED_INFO("left out"); + CHECK_FALSE(refs->list()->getAllData()[1]->getColumn(2)->getValue().asBoolean()); + UNSCOPED_INFO("its line as it is"); + CHECK(cell(refs->list()->getAllData()[1], 3) == std::string("x = count;")); refs->setKept(2, false); refs->setKept(2, true); rename->onCommit(); - ensure("made at the rest, by their order in what was found", applied && made == std::vector({ 0, 2 })); - ensure("done previewing", !refs->previewing() && !rename->getVisible()); - ensure_equals("listed as found again", head().find("total"), std::string::npos); + { + INFO("made at the rest, by their order in what was found"); + CHECK(applied); + CHECK(made == std::vector({ 0, 2 })); + } + { + INFO("done previewing"); + CHECK_FALSE(refs->previewing()); + CHECK_FALSE(rename->getVisible()); + } + UNSCOPED_INFO("listed as found again"); + CHECK(head().find("total") == std::string::npos); applied = false; refs->preview(found(), "total", "Rename 3 places", [&](const std::vector&) { applied = true; }); - ensure("no Show Changes where none is given", !window.find("references_changes")->getVisible()); + UNSCOPED_INFO("no Show Changes where none is given"); + CHECK_FALSE(window.find("references_changes")->getVisible()); window.find("references_cancel")->onCommit(); - ensure("Cancel makes nothing", !applied && !refs->previewing()); + { + INFO("Cancel makes nothing"); + CHECK_FALSE(applied); + CHECK_FALSE(refs->previewing()); + } // Show Changes: the chosen row's file, or the script's own with // none chosen, and the places kept. @@ -356,23 +404,32 @@ namespace tut shown = kept; }); LLButton* changes = window.find("references_changes"); - ensure("Show Changes shown", changes && changes->getVisible()); + { + INFO("Show Changes shown"); + REQUIRE(changes); + CHECK(changes->getVisible()); + } changes->onCommit(); refs->list()->selectNthItem(2); refs->setKept(1, false); refs->list()->selectNthItem(2); changes->onCommit(); - ensure("its own, then the chosen row's", files == std::vector({ std::string(), B })); - ensure("with the places kept", shown == std::vector({ 0, 2 })); + UNSCOPED_INFO("its own, then the chosen row's"); + CHECK(files == std::vector({ std::string(), B })); + UNSCOPED_INFO("with the places kept"); + CHECK(shown == std::vector({ 0, 2 })); window.find("references_cancel")->onCommit(); - ensure("gone with the preview", !changes->getVisible()); - ensure("each row as found", cell(refs->list()->getAllData()[0], 3) == "integer count;" && refs->list()->getAllData()[0]->getColumn(2)->getValue().asString() != "1"); + UNSCOPED_INFO("gone with the preview"); + CHECK_FALSE(changes->getVisible()); + { + INFO("each row as found"); + CHECK(cell(refs->list()->getAllData()[0], 3) == "integer count;"); + CHECK(refs->list()->getAllData()[0]->getColumn(2)->getValue().asString() != "1"); + } } - template<> template<> - void alscriptreferencespane_object::test<7>() + TEST_CASE_METHOD(alscriptreferencespane_data, "alscriptreferencespane: places slid out of sight are listed again once the list is seen", "[alscriptreferencespane]") { - set_test_name("places slid out of sight are listed again once the list is seen, not before"); ALScriptReferencesPane* refs = pane(); Doc& a = tab("a", "integer count;\nx = count;\n"); tab("b", "foo() { count; }\n"); @@ -383,17 +440,18 @@ namespace tut tabs->selectTabByName("problems_tab"); a.editor->replaceAll({ { ALTextRange(ALTextPos(0, 0), ALTextPos(0, 0)), "// one\n" } }); refs->pump(); - ensure_equals("slid, not listed again unseen", lines(), joined(Names{ "A 1:9", "A 2:5", "B 1:9" })); - ensure_equals("the places themselves slid", refs->found().places[0].span.line, 1); + UNSCOPED_INFO("slid, not listed again unseen"); + CHECK(lines() == joined(Names{ "A 1:9", "A 2:5", "B 1:9" })); + UNSCOPED_INFO("the places themselves slid"); + CHECK(refs->found().places[0].span.line == 1); tabs->selectTabByName("references_tab"); refs->pump(); - ensure_equals("seen: listed again", lines(), joined(Names{ "A 2:9", "A 3:5", "B 1:9" })); + UNSCOPED_INFO("seen: listed again"); + CHECK(lines() == joined(Names{ "A 2:9", "A 3:5", "B 1:9" })); } - template<> template<> - void alscriptreferencespane_object::test<8>() + TEST_CASE_METHOD(alscriptreferencespane_data, "alscriptreferencespane: a slid place keeps its row and at most a thousand places are listed", "[alscriptreferencespane]") { - set_test_name("a place slid is the same row, made again where it says something else; a thousand places listed at most, the rest counted"); ALScriptReferencesPane* refs = pane(); Doc& a = tab("a", "integer count;\nx = count;\n"); tab("b", "foo() { count; }\n"); @@ -406,10 +464,17 @@ namespace tut refs->list()->selectByValue(LLSD(2)); a.editor->replaceAll({ { ALTextRange(ALTextPos(0, 0), ALTextPos(0, 0)), "// one\n" } }); refs->pump(); - ensure_equals("slid", lines(), joined(Names{ "A 2:9", "A 3:5", "B 1:9" })); - ensure("the same rows", refs->list()->rowWithKey("2") == second && refs->list()->rowWithKey("3") == in_b); - ensure("one not moved, its cells as they were", in_b->getColumn(1) == b_line); - ensure("the row chosen kept", refs->list()->getFirstSelected() == second); + UNSCOPED_INFO("slid"); + CHECK(lines() == joined(Names{ "A 2:9", "A 3:5", "B 1:9" })); + { + INFO("the same rows"); + CHECK(refs->list()->rowWithKey("2") == second); + CHECK(refs->list()->rowWithKey("3") == in_b); + } + UNSCOPED_INFO("one not moved, its cells as they were"); + CHECK(in_b->getColumn(1) == b_line); + UNSCOPED_INFO("the row chosen kept"); + CHECK(refs->list()->getFirstSelected() == second); Found many; many.from = "a"; @@ -420,8 +485,10 @@ namespace tut many.places.push_back(place("", i, 0, "count;")); } refs->show(many); - ensure_equals("a thousand and one row saying the rest", refs->list()->getItemCount(), 1001); - ensure_equals("the rest", cell(refs->list()->getAllData().back(), 3), window.services().counted("ReferencesUnlisted", 3)); - ensure("all of them counted over the list", head().find("1003") != std::string::npos); + UNSCOPED_INFO("a thousand and one row saying the rest"); + CHECK(refs->list()->getItemCount() == 1001); + CHECK(cell(refs->list()->getAllData().back(), 3) == window.services().counted("ReferencesUnlisted", 3)); + UNSCOPED_INFO("all of them counted over the list"); + CHECK(head().find("1003") != std::string::npos); } } diff --git a/indra/newview/tests/alscriptregionusage_test.cpp b/indra/newview/tests/alscriptregionusage_test.cpp index cab391ef1e..5e1c3d1616 100644 --- a/indra/newview/tests/alscriptregionusage_test.cpp +++ b/indra/newview/tests/alscriptregionusage_test.cpp @@ -26,12 +26,12 @@ #include "../alscriptregionusage.h" -#include "../test/lltut.h" +#include #include #include -namespace tut +namespace { struct alscriptregionusage_data { @@ -97,14 +97,8 @@ namespace tut } }; - typedef test_group alscriptregionusage_group; - typedef alscriptregionusage_group::object alscriptregionusage_object; - tut::alscriptregionusage_group alscriptregionusage_test("ALScriptRegionUsage"); - - template<> template<> - void alscriptregionusage_object::test<1>() + TEST_CASE_METHOD(alscriptregionusage_data, "ALScriptRegionUsage: readAttachments and readDetails read each listed object's memory and URLs", "[ALScriptRegionUsage]") { - set_test_name("each object an answer lists, by id, with its memory and URLs: the attachments' by point, a parcel's details by parcel"); LLSD attachments; attachments["attachments"][0]["location"] = "Chest"; attachments["attachments"][0]["objects"].append(object(id(1), 65536, 1)); @@ -115,9 +109,18 @@ namespace tut const LLDate when(1.8e9); Usage::usages_t read; Usage::readAttachments(attachments, when, read); - ensure_equals("both", read.size(), 2U); - ensure("the chest's", read[id(1)].memory == 65536 && read[id(1)].urls == 1 && read[id(1)].when == when); - ensure("the HUD's", read[id(2)].memory == 131072 && read[id(2)].urls == 0); + REQUIRE(read.size() == 2U); + { + INFO("the chest's"); + CHECK(read[id(1)].memory == 65536); + CHECK(read[id(1)].urls == 1); + CHECK(read[id(1)].when == when); + } + { + INFO("the HUD's"); + CHECK(read[id(2)].memory == 131072); + CHECK(read[id(2)].urls == 0); + } LLSD details; details["parcels"][0]["name"] = "Home"; @@ -126,64 +129,88 @@ namespace tut details["parcels"][0]["objects"].append(object(LLUUID::null, 1, 1)); read.clear(); Usage::readDetails(details, when, read); - ensure("a parcel's, one without an id passed over", read.size() == 1 && read[id(3)].memory == 16384); + { + INFO("a parcel's, one without an id passed over"); + REQUIRE(read.size() == 1); + CHECK(read[id(3)].memory == 16384); + } read.clear(); Usage::readDetails(LLSD(), when, read); Usage::readAttachments(LLSD("nonsense"), when, read); - ensure("nothing from nothing", read.empty()); + UNSCOPED_INFO("nothing from nothing"); + REQUIRE(read.empty()); } - template<> template<> - void alscriptregionusage_object::test<2>() + TEST_CASE_METHOD(alscriptregionusage_data, "ALScriptRegionUsage: ask asks once for all attachments and once per object on land", "[ALScriptRegionUsage]") { - set_test_name("asked: the attachments once for all of them, each object on land by itself, none out of sight; heard and kept"); kinds[id(1)] = Kind::Attachment; kinds[id(2)] = Kind::Attachment; kinds[id(3)] = Kind::Land; kinds[id(4)] = Kind::Land; usage.ask({ id(1), id(2), id(3), id(4), id(5) }, 100.0); - ensure_equals("one asking for the attachments", attachmentAsks.size(), 1U); - ensure("each on land", landAsks.size() == 2 && landAsks[0].first == id(3) && landAsks[1].first == id(4)); - ensure("nothing known yet", !usage.usageOf(id(1)) && heard == 0); + UNSCOPED_INFO("one asking for the attachments"); + REQUIRE(attachmentAsks.size() == 1U); + { + INFO("each on land"); + REQUIRE(landAsks.size() == 2); + CHECK(landAsks[0].first == id(3)); + CHECK(landAsks[1].first == id(4)); + } + { + INFO("nothing known yet"); + REQUIRE_FALSE(usage.usageOf(id(1))); + CHECK(heard == 0); + } LLSD answer; answer["attachments"][0]["objects"].append(object(id(1), 65536, 2)); answer["attachments"][0]["objects"].append(object(id(2), 16384, 0)); attachmentAsks[0](answer); - ensure("heard", heard == 1 && usage.usageOf(id(1)) && usage.usageOf(id(1))->urls == 2 && usage.usageOf(id(2))->memory == 16384); + CHECK(heard == 1); + REQUIRE(usage.usageOf(id(1))); + REQUIRE(usage.usageOf(id(1))->urls == 2); + CHECK(usage.usageOf(id(2))->memory == 16384); LLSD parcel; parcel["parcels"][0]["objects"].append(object(id(3), 131072, 0)); parcel["parcels"][0]["objects"].append(object(id(4), 65536, 0)); landAsks[0].second(parcel); - ensure("a parcel's answer is every object on it", heard == 2 && usage.usageOf(id(4)) && usage.usageOf(id(4))->memory == 65536); + { + INFO("a parcel's answer is every object on it"); + CHECK(heard == 2); + REQUIRE(usage.usageOf(id(4))); + CHECK(usage.usageOf(id(4))->memory == 65536); + } } - template<> template<> - void alscriptregionusage_object::test<3>() + TEST_CASE_METHOD(alscriptregionusage_data, "ALScriptRegionUsage: an object is asked about at most once a minute counted from the answer", "[ALScriptRegionUsage]") { - set_test_name("once a minute: an object asked again only after, one another answer named counted as asked, and none after it is gone"); kinds[id(3)] = Kind::Land; kinds[id(4)] = Kind::Land; usage.ask({ id(3) }, 100.0); - ensure_equals("asked", landAsks.size(), 1U); + REQUIRE(landAsks.size() == 1U); usage.ask({ id(3) }, 130.0); - ensure_equals("not again within the minute", landAsks.size(), 1U); + UNSCOPED_INFO("not again within the minute"); + REQUIRE(landAsks.size() == 1U); LLSD parcel; parcel["parcels"][0]["objects"].append(object(id(3), 1, 0)); parcel["parcels"][0]["objects"].append(object(id(4), 2, 0)); landAsks[0].second(parcel); usage.ask({ id(4) }, 140.0); - ensure_equals("one the answer named is answered already", landAsks.size(), 1U); + UNSCOPED_INFO("one the answer named is answered already"); + REQUIRE(landAsks.size() == 1U); usage.ask({ id(3), id(4) }, 161.0); - ensure_equals("a minute from the answer, not the asking", landAsks.size(), 1U); + UNSCOPED_INFO("a minute from the answer, not the asking"); + REQUIRE(landAsks.size() == 1U); usage.ask({ id(3), id(4) }, 191.0); - ensure_equals("after the minute, each again", landAsks.size(), 3U); + UNSCOPED_INFO("after the minute, each again"); + REQUIRE(landAsks.size() == 3U); kinds[id(1)] = Kind::Attachment; kinds[id(2)] = Kind::Attachment; usage.ask({ id(1) }, 200.0); usage.ask({ id(2) }, 210.0); - ensure_equals("the attachments once a minute, whichever asks", attachmentAsks.size(), 1U); + UNSCOPED_INFO("the attachments once a minute, whichever asks"); + REQUIRE(attachmentAsks.size() == 1U); // An answer after it is gone goes nowhere. Told late = landAsks.back().second; @@ -197,13 +224,12 @@ namespace tut other.reset(); orphaned(parcel); late(parcel); - ensure("the one still here heard", usage.usageOf(id(3)) != nullptr); + UNSCOPED_INFO("the one still here heard"); + REQUIRE(usage.usageOf(id(3)) != nullptr); } - template<> template<> - void alscriptregionusage_object::test<4>() + TEST_CASE_METHOD(alscriptregionusage_data, "ALScriptRegionUsage: an estate manager asks each owner's script time once a minute", "[ALScriptRegionUsage]") { - set_test_name("time, of an estate manager only: each owner's objects asked for at once, once a minute, an owner unknown not at all; kept with what memory said, and memory kept with it"); kinds[id(1)] = Kind::Land; kinds[id(2)] = Kind::Land; kinds[id(3)] = Kind::Attachment; @@ -212,18 +238,29 @@ namespace tut owners[id(2)] = "Ann Resident"; owners[id(3)] = "Bo Smith"; usage.ask({ id(1), id(2), id(3), id(4) }, 100.0); - ensure("not an estate manager: not asked", timeAsks.empty()); + UNSCOPED_INFO("not an estate manager: not asked"); + REQUIRE(timeAsks.empty()); estateManager = true; attachmentAsks.clear(); landAsks.clear(); usage.ask({ id(1), id(2), id(3), id(4) }, 100.0); - ensure_equals("an asking an owner", timeAsks.size(), 2U); - ensure("each owner once, none for one unknown", timeAsks[0].first == "Ann Resident" && timeAsks[1].first == "Bo Smith"); + UNSCOPED_INFO("an asking an owner"); + REQUIRE(timeAsks.size() == 2U); + { + INFO("each owner once, none for one unknown"); + CHECK(timeAsks[0].first == "Ann Resident"); + CHECK(timeAsks[1].first == "Bo Smith"); + } usage.ask({ id(1) }, 130.0); - ensure("not again within a minute", timeAsks.size() == 2); + UNSCOPED_INFO("not again within a minute"); + REQUIRE(timeAsks.size() == 2); usage.ask({ id(1) }, 161.0); - ensure("again after it", timeAsks.size() == 3 && timeAsks[2].first == "Ann Resident"); + { + INFO("again after it"); + REQUIRE(timeAsks.size() == 3); + CHECK(timeAsks[2].first == "Ann Resident"); + } // Memory first, then time: both kept. LLSD details; @@ -234,13 +271,22 @@ namespace tut times[id(5)] = 2.0f; const S32 before = heard; timeAsks[0].second(times); - ensure("heard", heard == before + 1); + CHECK(heard == before + 1); const ALScriptRegionUsage::Usage* one = usage.usageOf(id(1)); - ensure("its time and its memory", one && one->hasTime() && one->time == 0.125f && one->hasMemory() && one->memory == 65536); - ensure("an object not asked about kept too, time alone", usage.usageOf(id(5)) && !usage.usageOf(id(5))->hasMemory()); + REQUIRE(one); + REQUIRE(one->hasTime()); + REQUIRE(one->time == 0.125f); + REQUIRE(one->hasMemory()); + CHECK(one->memory == 65536); + { + INFO("an object not asked about kept too, time alone"); + REQUIRE(usage.usageOf(id(5))); + CHECK_FALSE(usage.usageOf(id(5))->hasMemory()); + } // Memory again: the time stays. landAsks[0].second(details); - ensure("time kept through memory", usage.usageOf(id(1))->time == 0.125f); + UNSCOPED_INFO("time kept through memory"); + CHECK(usage.usageOf(id(1))->time == 0.125f); // A later answer for the owner that leaves one of theirs out: it // runs no scripts now. One as full as Top Scripts gives proves @@ -248,38 +294,53 @@ namespace tut Usage::times_t later; later[id(2)] = 0.5f; timeAsks[0].second(later); - ensure("left out: no time now, its memory kept", !usage.usageOf(id(1))->hasTime() && usage.usageOf(id(1))->hasMemory()); - ensure("named: its time", usage.usageOf(id(2))->time == 0.5f); + { + INFO("left out: no time now, its memory kept"); + REQUIRE_FALSE(usage.usageOf(id(1))->hasTime()); + CHECK(usage.usageOf(id(1))->hasMemory()); + } + UNSCOPED_INFO("named: its time"); + CHECK(usage.usageOf(id(2))->time == 0.5f); Usage::times_t full; for (U32 n = 0; n < ALScriptRegionUsage::TOP_SCRIPTS_MOST; ++n) { full[id(100 + n)] = 0.01f; } timeAsks[0].second(full); - ensure("a full answer leaving it out: its time stands", usage.usageOf(id(2))->time == 0.5f); + UNSCOPED_INFO("a full answer leaving it out: its time stands"); + CHECK(usage.usageOf(id(2))->time == 0.5f); } - template<> template<> - void alscriptregionusage_object::test<5>() + TEST_CASE_METHOD(alscriptregionusage_data, "ALScriptRegionUsage: ownerKey and answering match a Top Scripts answer to the right ask", "[ALScriptRegionUsage]") { - set_test_name("an answer of Top Scripts by owner: with Top Objects not waiting on one, the oldest ask's whose owner its rows name, else the oldest's; with it waiting, its own but for one naming an owner only the studio asked about"); typedef std::optional Whose; typedef std::optional Theirs; - ensure_equals("a name in either form, lowered", ALScriptRegionUsage::ownerKey("Ann Resident "), std::string("ann")); - ensure_equals("a last name kept", ALScriptRegionUsage::ownerKey("Bo Smith"), std::string("bo.smith")); - ensure_equals("a username as it is", ALScriptRegionUsage::ownerKey("Bo.Smith"), std::string("bo.smith")); + UNSCOPED_INFO("a name in either form, lowered"); + CHECK(ALScriptRegionUsage::ownerKey("Ann Resident ") == std::string("ann")); + UNSCOPED_INFO("a last name kept"); + CHECK(ALScriptRegionUsage::ownerKey("Bo Smith") == std::string("bo.smith")); + UNSCOPED_INFO("a username as it is"); + CHECK(ALScriptRegionUsage::ownerKey("Bo.Smith") == std::string("bo.smith")); const std::vector waiting = { "Ann Resident", "Bo Smith", "Ann Resident" }; const Theirs none; - ensure("the oldest it names", ALScriptRegionUsage::answering(waiting, { "ann ", "ann " }, none) == Whose(0)); - ensure("not the oldest where it names a later one", ALScriptRegionUsage::answering(waiting, { "Bo Smith" }, none) == Whose(1)); - ensure("no rows, Top Objects not waiting: the oldest's", ALScriptRegionUsage::answering(waiting, {}, none) == Whose(0)); - ensure("named otherwise, Top Objects not waiting: the oldest's still", ALScriptRegionUsage::answering(waiting, { "Cy Resident" }, none) == Whose(0)); - ensure("none waiting: none", !ALScriptRegionUsage::answering({}, { "Ann Resident" }, none)); + UNSCOPED_INFO("the oldest it names"); + CHECK(ALScriptRegionUsage::answering(waiting, { "ann ", "ann " }, none) == Whose(0)); + UNSCOPED_INFO("not the oldest where it names a later one"); + CHECK(ALScriptRegionUsage::answering(waiting, { "Bo Smith" }, none) == Whose(1)); + UNSCOPED_INFO("no rows, Top Objects not waiting: the oldest's"); + CHECK(ALScriptRegionUsage::answering(waiting, {}, none) == Whose(0)); + UNSCOPED_INFO("named otherwise, Top Objects not waiting: the oldest's still"); + CHECK(ALScriptRegionUsage::answering(waiting, { "Cy Resident" }, none) == Whose(0)); + UNSCOPED_INFO("none waiting: none"); + REQUIRE_FALSE(ALScriptRegionUsage::answering({}, { "Ann Resident" }, none)); const Theirs cy = std::string("cy"); - ensure("Top Objects waiting: one naming the studio's owner alone is the studio's", ALScriptRegionUsage::answering(waiting, { "Bo Smith" }, cy) == Whose(1)); - ensure("one naming its own owner: Top Objects'", !ALScriptRegionUsage::answering(waiting, { "Cy Resident" }, cy)); - ensure("no rows: Top Objects'", !ALScriptRegionUsage::answering(waiting, {}, cy)); - ensure("an owner both asked about: Top Objects', the studio's answer to come", - !ALScriptRegionUsage::answering(waiting, { "Bo Smith" }, Theirs(std::string("Bo Smith")))); + UNSCOPED_INFO("Top Objects waiting: one naming the studio's owner alone is the studio's"); + CHECK(ALScriptRegionUsage::answering(waiting, { "Bo Smith" }, cy) == Whose(1)); + UNSCOPED_INFO("one naming its own owner: Top Objects'"); + REQUIRE_FALSE(ALScriptRegionUsage::answering(waiting, { "Cy Resident" }, cy)); + UNSCOPED_INFO("no rows: Top Objects'"); + REQUIRE_FALSE(ALScriptRegionUsage::answering(waiting, {}, cy)); + UNSCOPED_INFO("an owner both asked about: Top Objects', the studio's answer to come"); + REQUIRE_FALSE(ALScriptRegionUsage::answering(waiting, { "Bo Smith" }, Theirs(std::string("Bo Smith")))); } } diff --git a/indra/newview/tests/alscriptsaveflow_test.cpp b/indra/newview/tests/alscriptsaveflow_test.cpp index da21490d6d..f02c827727 100644 --- a/indra/newview/tests/alscriptsaveflow_test.cpp +++ b/indra/newview/tests/alscriptsaveflow_test.cpp @@ -26,9 +26,9 @@ #include "../alscriptsaveflow.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { struct alscriptsaveflow_data { @@ -71,195 +71,245 @@ namespace tut return flow.compiled(answer); } }; - typedef test_group alscriptsaveflow_group; - typedef alscriptsaveflow_group::object alscriptsaveflow_object; - alscriptsaveflow_group alscriptsaveflow_instance("ALScriptSaveFlow"); - template<> template<> - void alscriptsaveflow_object::test<1>() + TEST_CASE_METHOD(alscriptsaveflow_data, "ALScriptSaveFlow: a Save asked during a send runs once after it however often it was asked", "[ALScriptSaveFlow]") { - set_test_name("a Save while one is sent follows it once it answers, and once however often it was asked"); - ensure("goes", flow.ask(false, false) == Flow::Start::Go); - ensure("straight up", flow.route(script(1)) == Route::Send); + CHECK(flow.ask(false, false) == Flow::Start::Go); + UNSCOPED_INFO("straight up"); + CHECK(flow.route(script(1)) == Route::Send); send(); - ensure("sending", flow.sending()); - ensure("asked again: queued", flow.ask(false, false) == Flow::Start::Queued); - ensure("and again", flow.ask(false, false) == Flow::Start::Queued); + CHECK(flow.sending()); + UNSCOPED_INFO("asked again: queued"); + CHECK(flow.ask(false, false) == Flow::Start::Queued); + UNSCOPED_INFO("and again"); + CHECK(flow.ask(false, false) == Flow::Start::Queued); const Flow::Landing landing = compile(true, true); - ensure("its own save, saved", landing.ours && landing.markSaved && !landing.stopped); - ensure("nothing on its way", !flow.underway()); - ensure("the one asked for meanwhile", flow.takeAgain()); - ensure("once", !flow.takeAgain()); + { + INFO("its own save, saved"); + CHECK(landing.ours); + CHECK(landing.markSaved); + CHECK_FALSE(landing.stopped); + } + CHECK_FALSE(flow.underway()); + UNSCOPED_INFO("the one asked for meanwhile"); + CHECK(flow.takeAgain()); + UNSCOPED_INFO("once"); + CHECK_FALSE(flow.takeAgain()); const Flow::Landing recompile = compile(true, true); - ensure("a recompile from elsewhere is not this tab's save", !recompile.ours && !recompile.markSaved); + { + INFO("a recompile from elsewhere is not this tab's save"); + CHECK_FALSE(recompile.ours); + CHECK_FALSE(recompile.markSaved); + } } - template<> template<> - void alscriptsaveflow_object::test<2>() + TEST_CASE_METHOD(alscriptsaveflow_data, "ALScriptSaveFlow: Save Anyway lets one text past the analyzers' errors (S3-D1)", "[ALScriptSaveFlow]") { - set_test_name("Save Anyway lets past the check that stopped the save, for that text (S3-D1); nothing the preprocessor finds stops one"); - ensure("the analyzers' errors stop it", flow.route(expanded(1, true, true, 2)) == Route::StoppedByAnalyzers); - ensure("and nothing waits", !flow.underway()); + UNSCOPED_INFO("the analyzers' errors stop it"); + CHECK(flow.route(expanded(1, true, true, 2)) == Route::StoppedByAnalyzers); + CHECK_FALSE(flow.underway()); flow.letPast(1); - ensure("asked again: past the errors, on to the preprocessor", flow.route(expanded(1, true, true, 2)) == Route::Preprocess); - ensure("whatever it found, sent: the save keeps the work", flow.preprocessed(run(1, 1)) == Flow::Landed::Send); - ensure("the same text asked again: still past", flow.route(expanded(1, true, true, 2)) == Route::Preprocess); - ensure("a changed text asks afresh", flow.route(expanded(2, true, true, 2)) == Route::StoppedByAnalyzers); + UNSCOPED_INFO("asked again: past the errors, on to the preprocessor"); + CHECK(flow.route(expanded(1, true, true, 2)) == Route::Preprocess); + UNSCOPED_INFO("whatever it found, sent: the save keeps the work"); + CHECK(flow.preprocessed(run(1, 1)) == Flow::Landed::Send); + UNSCOPED_INFO("the same text asked again: still past"); + CHECK(flow.route(expanded(1, true, true, 2)) == Route::Preprocess); + UNSCOPED_INFO("a changed text asks afresh"); + CHECK(flow.route(expanded(2, true, true, 2)) == Route::StoppedByAnalyzers); flow.letPast(2); - ensure("let past", flow.route(expanded(2, true, true, 2)) == Route::Preprocess); + CHECK(flow.route(expanded(2, true, true, 2)) == Route::Preprocess); flow.preprocessed(run(2, 2)); send(); compile(true, true); - ensure("and a save that went up leaves nothing let past", flow.route(expanded(2, true, true, 2)) == Route::StoppedByAnalyzers); + UNSCOPED_INFO("and a save that went up leaves nothing let past"); + CHECK(flow.route(expanded(2, true, true, 2)) == Route::StoppedByAnalyzers); } - template<> template<> - void alscriptsaveflow_object::test<3>() + TEST_CASE_METHOD(alscriptsaveflow_data, "ALScriptSaveFlow: a save held on errors waits for a check of its text and is asked again", "[ALScriptSaveFlow]") { - set_test_name("a save held on errors waits for a check of its text, and is asked again when it comes"); - ensure("not checked yet", flow.route(script(4, true, false)) == Route::Check); - ensure("waiting", flow.stage() == Flow::Stage::Checking); - ensure("the check came: asked again", flow.checked()); - ensure("once", !flow.checked()); - ensure("checked, and clean: sent", flow.route(script(4, true, true, 0)) == Route::Send); + UNSCOPED_INFO("not checked yet"); + CHECK(flow.route(script(4, true, false)) == Route::Check); + CHECK(flow.stage() == Flow::Stage::Checking); + UNSCOPED_INFO("the check came: asked again"); + CHECK(flow.checked()); + UNSCOPED_INFO("once"); + CHECK_FALSE(flow.checked()); + UNSCOPED_INFO("checked, and clean: sent"); + CHECK(flow.route(script(4, true, true, 0)) == Route::Send); } - template<> template<> - void alscriptsaveflow_object::test<4>() + TEST_CASE_METHOD(alscriptsaveflow_data, "ALScriptSaveFlow: a text changed while preprocessed starts over and a running pass is joined", "[ALScriptSaveFlow]") { - set_test_name("a text changed while it is preprocessed starts the save over; a run already on its way is joined; a run nobody saves with is left alone"); - ensure("run", flow.route(expanded(1)) == Route::Preprocess); - ensure("typed in meanwhile: from the start", flow.preprocessed(run(1, 2)) == Flow::Landed::MovedOn); - ensure("nothing on its way", !flow.underway()); - ensure("a view's run, nobody saving", flow.preprocessed(run(2, 2)) == Flow::Landed::NotForSave); - ensure("a run on its way already: joined", flow.route(expanded(2, false, true, 0, true)) == Route::JoinPreprocessor); - ensure("waiting on it", flow.stage() == Flow::Stage::Preprocessing); - ensure("and sent when it comes", flow.preprocessed(run(2, 2)) == Flow::Landed::Send); + CHECK(flow.route(expanded(1)) == Route::Preprocess); + UNSCOPED_INFO("typed in meanwhile: from the start"); + CHECK(flow.preprocessed(run(1, 2)) == Flow::Landed::MovedOn); + CHECK_FALSE(flow.underway()); + UNSCOPED_INFO("a view's run, nobody saving"); + CHECK(flow.preprocessed(run(2, 2)) == Flow::Landed::NotForSave); + UNSCOPED_INFO("a run on its way already: joined"); + CHECK(flow.route(expanded(2, false, true, 0, true)) == Route::JoinPreprocessor); + CHECK(flow.stage() == Flow::Stage::Preprocessing); + UNSCOPED_INFO("and sent when it comes"); + CHECK(flow.preprocessed(run(2, 2)) == Flow::Landed::Send); } - template<> template<> - void alscriptsaveflow_object::test<5>() + TEST_CASE_METHOD(alscriptsaveflow_data, "ALScriptSaveFlow: a close waiting on a save is dropped when the save stops unless quitting", "[ALScriptSaveFlow]") { - set_test_name("a close waiting on a save goes where the save stops, and not where the viewer is quitting over a failed compile"); flow.setCloseAfter(true); flow.route(script(1)); flow.stopped(); - ensure("refused as it was sent", !flow.closeAfter() && !flow.underway()); + { + INFO("refused as it was sent"); + CHECK_FALSE(flow.closeAfter()); + CHECK_FALSE(flow.underway()); + } flow.setCloseAfter(true); send(); flow.ask(false, false); - ensure("the text did not go up: stopped", compile(false, false).stopped); - ensure("the close with it", !flow.closeAfter()); - ensure("and what was asked meanwhile", !flow.takeAgain()); + UNSCOPED_INFO("the text did not go up: stopped"); + CHECK(compile(false, false).stopped); + UNSCOPED_INFO("the close with it"); + CHECK_FALSE(flow.closeAfter()); + UNSCOPED_INFO("and what was asked meanwhile"); + CHECK_FALSE(flow.takeAgain()); flow.setCloseAfter(true); send(); - ensure("up, and did not compile: stopped", compile(true, false).stopped && !flow.closeAfter()); + { + INFO("up, and did not compile: stopped"); + CHECK(compile(true, false).stopped); + CHECK_FALSE(flow.closeAfter()); + } flow.setCloseAfter(true); send(); const Flow::Landing quitting = compile(true, false, true); - ensure("unless quitting: saved, and closed", !quitting.stopped && quitting.markSaved && flow.closeAfter()); + { + INFO("unless quitting: saved, and closed"); + CHECK_FALSE(quitting.stopped); + CHECK(quitting.markSaved); + CHECK(flow.closeAfter()); + } flow.setCloseAfter(true); send(); compile(true, true); - ensure("compiled: closed", flow.closeAfter()); + UNSCOPED_INFO("compiled: closed"); + CHECK(flow.closeAfter()); } - template<> template<> - void alscriptsaveflow_object::test<6>() + TEST_CASE_METHOD(alscriptsaveflow_data, "ALScriptSaveFlow: an external editor's save skips every check and out of reach nothing goes", "[ALScriptSaveFlow]") { - set_test_name("an external editor's save goes past every check and is the editor's until it lands; out of reach, or detached, nothing goes"); flow.fromExternal(3); - ensure("the editor's", flow.external()); - ensure("past the errors", flow.route(expanded(3, true, true, 5)) == Route::Preprocess); - ensure("and sent", flow.preprocessed(run(3, 3)) == Flow::Landed::Send); + CHECK(flow.external()); + UNSCOPED_INFO("past the errors"); + CHECK(flow.route(expanded(3, true, true, 5)) == Route::Preprocess); + UNSCOPED_INFO("and sent"); + CHECK(flow.preprocessed(run(3, 3)) == Flow::Landed::Send); flow.endExternal(); - ensure("ended", !flow.external()); + CHECK_FALSE(flow.external()); flow.setCloseAfter(true); - ensure("out of reach", flow.ask(true, false) == Flow::Start::OutOfReach && !flow.closeAfter()); + CHECK(flow.ask(true, false) == Flow::Start::OutOfReach); + CHECK_FALSE(flow.closeAfter()); flow.setCloseAfter(true); - ensure("detached", flow.ask(false, true) == Flow::Start::Detached && !flow.closeAfter()); + CHECK(flow.ask(false, true) == Flow::Start::Detached); + CHECK_FALSE(flow.closeAfter()); } - template<> template<> - void alscriptsaveflow_object::test<7>() + TEST_CASE_METHOD(alscriptsaveflow_data, "ALScriptSaveFlow: files and notecards go as they stand and errors stop only a held save", "[ALScriptSaveFlow]") { - set_test_name("a file is written and a notecard sent as they stand, never checked or preprocessed; not held, errors stop nothing"); Flow::Tab file = expanded(1, true, false, 3); file.file = true; - ensure("a file", flow.route(file) == Route::File); + CHECK(flow.route(file) == Route::File); Flow::Tab card = expanded(1, true, false, 3); card.notecard = true; - ensure("a notecard", flow.route(card) == Route::Notecard); - ensure("not held: sent over errors", flow.route(script(1, false, true, 3)) == Route::Send); - ensure("or expanded first", flow.route(expanded(1, false, true, 3)) == Route::Preprocess); + CHECK(flow.route(card) == Route::Notecard); + UNSCOPED_INFO("not held: sent over errors"); + CHECK(flow.route(script(1, false, true, 3)) == Route::Send); + UNSCOPED_INFO("or expanded first"); + CHECK(flow.route(expanded(1, false, true, 3)) == Route::Preprocess); } - template<> template<> - void alscriptsaveflow_object::test<8>() + TEST_CASE_METHOD(alscriptsaveflow_data, "ALScriptSaveFlow: fixOnce is true once a save and again for the next", "[ALScriptSaveFlow]") { - set_test_name("the safe fixes are made once a save, however many checks it waits on, and afresh for the next"); - ensure("once", flow.fixOnce()); + CHECK(flow.fixOnce()); flow.route(script(1, true, false)); flow.checked(); - ensure("not again while the same save waits", !flow.fixOnce()); + UNSCOPED_INFO("not again while the same save waits"); + CHECK_FALSE(flow.fixOnce()); send(); - ensure("afresh once it went", flow.fixOnce()); + UNSCOPED_INFO("afresh once it went"); + CHECK(flow.fixOnce()); flow.stopped(); - ensure("or stopped", flow.fixOnce()); + UNSCOPED_INFO("or stopped"); + CHECK(flow.fixOnce()); flow.done(); - ensure("or a file was written", flow.fixOnce()); + UNSCOPED_INFO("or a file was written"); + CHECK(flow.fixOnce()); } - template<> template<> - void alscriptsaveflow_object::test<9>() + TEST_CASE_METHOD(alscriptsaveflow_data, "ALScriptSaveFlow: sent keeps the save point and source map and a notecard's items", "[ALScriptSaveFlow]") { - set_test_name("what went up is kept: where the journal stood, the map the compiler's lines are read through, a notecard's items"); ALTextUndo::SavePoint at; at.serial = 7; LLUUID item; item.generate(); flow.sent(at, ALSourceMap(), { item }); - ensure("the save point", flow.savePoint().serial == 7); - ensure("the map", flow.sentMap().has_value()); - ensure("the items", flow.sentItems().size() == 1 && flow.sentItems().front() == item); + CHECK(flow.savePoint().serial == 7); + CHECK(flow.sentMap().has_value()); + REQUIRE(flow.sentItems().size() == 1); + CHECK(flow.sentItems().front() == item); flow.forgetSentItems(); - ensure("let go of", flow.sentItems().empty()); + UNSCOPED_INFO("let go of"); + CHECK(flow.sentItems().empty()); compile(true, true); - ensure("the map stays after, for what the script says as it runs", flow.sentMap().has_value()); + UNSCOPED_INFO("the map stays after, for what the script says as it runs"); + CHECK(flow.sentMap().has_value()); flow.setWarnWeightFor(9); - ensure("the weight to warn of", flow.warnWeightFor() == 9); + CHECK(flow.warnWeightFor() == 9); } - template<> template<> - void alscriptsaveflow_object::test<10>() + TEST_CASE_METHOD(alscriptsaveflow_data, "ALScriptSaveFlow: the world is asked once a text and a moved item stops the save", "[ALScriptSaveFlow]") { - set_test_name("the world asked once a text what the item holds; moved, the save stops until asked again; a save asked meanwhile waits on the answer"); Flow::Tab tab = script(1); tab.askWorld = true; - ensure("asked first", flow.route(tab) == Route::AskWorld && flow.stage() == Flow::Stage::Asking); - ensure("a save asked meanwhile waits on the answer", flow.ask(false, false) == Flow::Start::Queued); + { + INFO("asked first"); + CHECK(flow.route(tab) == Route::AskWorld); + CHECK(flow.stage() == Flow::Stage::Asking); + } + UNSCOPED_INFO("a save asked meanwhile waits on the answer"); + CHECK(flow.ask(false, false) == Flow::Start::Queued); flow.worldAnswered(1, false); - ensure("answered, nothing moved: sent", flow.route(tab) == Route::Send); + UNSCOPED_INFO("answered, nothing moved: sent"); + CHECK(flow.route(tab) == Route::Send); tab.version = 2; - ensure("a new text asks again", flow.route(tab) == Route::AskWorld); + UNSCOPED_INFO("a new text asks again"); + CHECK(flow.route(tab) == Route::AskWorld); flow.worldAnswered(2, true); - ensure("moved: stopped", flow.route(tab) == Route::StoppedByWorld && !flow.underway()); - ensure("and again", flow.route(tab) == Route::StoppedByWorld); + { + INFO("moved: stopped"); + CHECK(flow.route(tab) == Route::StoppedByWorld); + CHECK_FALSE(flow.underway()); + } + UNSCOPED_INFO("and again"); + CHECK(flow.route(tab) == Route::StoppedByWorld); flow.letPast(2); - ensure("asked again: saved over it", flow.route(tab) == Route::Send); + UNSCOPED_INFO("asked again: saved over it"); + CHECK(flow.route(tab) == Route::Send); Flow::Tab file = tab; file.file = true; - ensure("a file asks nothing of the world", flow.route(file) == Route::File); + UNSCOPED_INFO("a file asks nothing of the world"); + CHECK(flow.route(file) == Route::File); Flow::Tab card = script(3); card.notecard = true; card.askWorld = true; - ensure("a notecard is asked about too", flow.route(card) == Route::AskWorld); + UNSCOPED_INFO("a notecard is asked about too"); + CHECK(flow.route(card) == Route::AskWorld); } } diff --git a/indra/newview/tests/alscriptsearchpane_test.cpp b/indra/newview/tests/alscriptsearchpane_test.cpp index ce393de489..c79d345d47 100644 --- a/indra/newview/tests/alscriptsearchpane_test.cpp +++ b/indra/newview/tests/alscriptsearchpane_test.cpp @@ -35,7 +35,7 @@ #include "alscriptstudio_fixture.h" #include "lltabcontainer.h" -#include "../test/lltut.h" +#include #include #include @@ -151,7 +151,7 @@ namespace } } -namespace tut +namespace { struct alscriptsearchpane_data { @@ -165,11 +165,12 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } // The tab as the skin built it. pane = window.find("search_tab"); - ensure("the skin builds the tab as the pane", pane != nullptr); + UNSCOPED_INFO("the skin builds the tab as the pane"); + REQUIRE(pane != nullptr); // In sight, its tab the one chosen, as when it is worked in. window.find("bottom_tabs")->selectTabByName("search_tab"); return *pane; @@ -216,29 +217,37 @@ namespace tut } std::string counted() const { return pane->count()->getText(); } }; - typedef test_group alscriptsearchpane_group; - typedef alscriptsearchpane_group::object alscriptsearchpane_object; - alscriptsearchpane_group alscriptsearchpane_instance("ALScriptSearchPane"); - template <> - template <> - void alscriptsearchpane_object::test<1>() + TEST_CASE_METHOD(alscriptsearchpane_data, "ALScriptSearchPane: open scripts are searched as they stand and an object's are fetched", "[ALScriptSearchPane]") { - set_test_name("places found in the scripts open as they stand, and in an object's as fetched, a script open elsewhere as it stands there"); ALScriptSearchPane& out = make(); Doc& door = doc("door", "timer one\nnothing\n timer two\n"); find("timer"); - ensure_equals("one script", out.search().found().size(), size_t(1)); - ensure_equals("two places", out.search().found()[0].places.size(), size_t(2)); - ensure_equals("a row each", out.list()->getItemCount(), 2); + UNSCOPED_INFO("one script"); + REQUIRE(out.search().found().size() == size_t(1)); + UNSCOPED_INFO("two places"); + REQUIRE(out.search().found()[0].places.size() == size_t(2)); + UNSCOPED_INFO("a row each"); + CHECK(out.list()->getItemCount() == 2); // The list's keys: return goes to the place chosen, to type there, // escape back to the script; and the rows copy. out.list()->selectFirstItem(); - ensure("return", out.list()->handleKeyHere(KEY_RETURN, MASK_NONE) && !studio.chosen.empty() && studio.chosen.back().toEditor); - ensure("escape", out.list()->handleKeyHere(KEY_ESCAPE, MASK_NONE) && services.reveals.back()); - ensure("copied", out.list()->handleKeyHere('C', MASK_CONTROL)); - ensure_equals("the line as listed, trimmed", out.search().found()[0].lines[1], std::string("timer two")); - ensure_equals("counted", counted(), services.words("SearchCount", { { "[HITS]", services.counted("Matches", 2) }, + { + INFO("return"); + CHECK(out.list()->handleKeyHere(KEY_RETURN, MASK_NONE)); + REQUIRE_FALSE(studio.chosen.empty()); + CHECK(studio.chosen.back().toEditor); + } + { + INFO("escape"); + REQUIRE(out.list()->handleKeyHere(KEY_ESCAPE, MASK_NONE)); + CHECK(services.reveals.back()); + } + UNSCOPED_INFO("copied"); + CHECK(out.list()->handleKeyHere('C', MASK_CONTROL)); + UNSCOPED_INFO("the line as listed, trimmed"); + CHECK(out.search().found()[0].lines[1] == std::string("timer two")); + CHECK(counted() == services.words("SearchCount", { { "[HITS]", services.counted("Matches", 2) }, { "[FILES]", services.counted("Files", 1) }, { "[SHOWN]", "2000" } })); @@ -251,57 +260,69 @@ namespace tut studio.objects = { { root, "Thing", { door.ref, there.ref, away } } }; studio.inHand = root; find("timer", "object"); - ensure_equals("the open one and the one elsewhere", out.search().found().size(), size_t(2)); - ensure_equals("where it is elsewhere, said", out.search().found()[1].where, std::string("Thing: there.lsl")); - ensure_equals("the rest fetched", studio.fetches.size(), size_t(1)); - ensure("the one asked", studio.fetches[0].ref == away); - ensure_equals("counting while it comes", counted(), services.words("SearchCounting", { { "[HITS]", services.counted("Matches", 3) }, + UNSCOPED_INFO("the open one and the one elsewhere"); + REQUIRE(out.search().found().size() == size_t(2)); + UNSCOPED_INFO("where it is elsewhere, said"); + CHECK(out.search().found()[1].where == std::string("Thing: there.lsl")); + UNSCOPED_INFO("the rest fetched"); + REQUIRE(studio.fetches.size() == size_t(1)); + UNSCOPED_INFO("the one asked"); + CHECK(studio.fetches[0].ref == away); + UNSCOPED_INFO("counting while it comes"); + CHECK(counted() == services.words("SearchCounting", { { "[HITS]", services.counted("Matches", 3) }, { "[FILES]", services.counted("Files", 2) }, { "[SHOWN]", "2000" } })); out.fetched(studio.fetches[0].generation, "Thing", away, "away.lsl", std::make_shared("x\ntimer fetched\n"), false); - ensure_equals("fetched, found", out.search().found().size(), size_t(3)); - ensure_equals("its text kept, for a replace", out.search().found()[2].text, std::string("x\ntimer fetched\n")); - ensure_equals("none to wait for", out.search().pending(), 0); + UNSCOPED_INFO("fetched, found"); + REQUIRE(out.search().found().size() == size_t(3)); + UNSCOPED_INFO("its text kept, for a replace"); + CHECK(out.search().found()[2].text == std::string("x\ntimer fetched\n")); + UNSCOPED_INFO("none to wait for"); + CHECK(out.search().pending() == 0); // No object in hand: none guessed at. studio.inHand.setNull(); find("timer", "object"); - ensure_equals("said", counted(), services.words("SearchNoObject")); + CHECK(counted() == services.words("SearchNoObject")); } - template <> - template <> - void alscriptsearchpane_object::test<2>() + TEST_CASE_METHOD(alscriptsearchpane_data, "ALScriptSearchPane: a later search drops the answers to an earlier one", "[ALScriptSearchPane]") { - set_test_name("a later search drops what an earlier one is answered"); ALScriptSearchPane& out = make(); const ALScriptRef away(fresh(), fresh()); const LLUUID root = fresh(); studio.objects = { { root, "Thing", { away } } }; find("timer", "listed"); find("other", "listed"); - ensure_equals("asked twice", studio.fetches.size(), size_t(2)); + UNSCOPED_INFO("asked twice"); + REQUIRE(studio.fetches.size() == size_t(2)); out.fetched(studio.fetches[0].generation, "Thing", away, "away.lsl", std::make_shared("timer\nother\n"), false); - ensure("the first search's answer dropped", out.search().found().empty()); - ensure_equals("still waiting on the second's", out.search().pending(), 1); + UNSCOPED_INFO("the first search's answer dropped"); + CHECK(out.search().found().empty()); + UNSCOPED_INFO("still waiting on the second's"); + CHECK(out.search().pending() == 1); out.fetched(studio.fetches[1].generation, "Thing", away, "away.lsl", std::make_shared("timer\nother\n"), false); - ensure_equals("the second's kept", out.search().found().size(), size_t(1)); - ensure_equals("for what it looked for", out.search().found()[0].places[0].begin.line, 1); + UNSCOPED_INFO("the second's kept"); + REQUIRE(out.search().found().size() == size_t(1)); + UNSCOPED_INFO("for what it looked for"); + CHECK(out.search().found()[0].places[0].begin.line == 1); } - template <> - template <> - void alscriptsearchpane_object::test<3>() + TEST_CASE_METHOD(alscriptsearchpane_data, "ALScriptSearchPane: a script typed in since the search is left alone and searched again", "[ALScriptSearchPane]") { - set_test_name("a script typed in since the search is not replaced over, and is searched again a moment later"); ALScriptSearchPane& out = make(); Doc& door = doc("door", "timer one\ntimer two\n"); find("timer"); door.editor->setCaret(ALTextPos(0, 0)); door.editor->insertText("x"); out.replaceAll(); - ensure_equals("left alone", door.editor->text(), std::string("xtimer one\ntimer two\n")); - ensure("said so", !services.reports.empty() && services.reports.back().failure); + UNSCOPED_INFO("left alone"); + CHECK(door.editor->text() == std::string("xtimer one\ntimer two\n")); + { + INFO("said so"); + REQUIRE_FALSE(services.reports.empty()); + CHECK(services.reports.back().failure); + } // Searched again once the typing has rested: its places are where // they are now. @@ -310,26 +331,28 @@ namespace tut door.editor->insertText("yy"); out.typedIn(door); out.pump(); - ensure_equals("not at once", out.search().found()[0].places[0].begin.column, 1); + UNSCOPED_INFO("not at once"); + CHECK(out.search().found()[0].places[0].begin.column == 1); std::this_thread::sleep_for(std::chrono::milliseconds(700)); LLTabContainer* tabs = window.find("bottom_tabs"); tabs->selectTabByName("problems_tab"); out.pump(); - ensure_equals("due, but out of sight: waiting", out.search().found()[0].places[0].begin.column, 1); + UNSCOPED_INFO("due, but out of sight: waiting"); + CHECK(out.search().found()[0].places[0].begin.column == 1); tabs->selectTabByName("search_tab"); out.pump(); - ensure_equals("then, seen, where it is now", out.search().found()[0].places[0].begin.column, 3); + UNSCOPED_INFO("then, seen, where it is now"); + CHECK(out.search().found()[0].places[0].begin.column == 3); out.replaceAll(); - ensure_equals("and replaced", door.editor->text(), std::string("yyx one\n two\n")); + UNSCOPED_INFO("and replaced"); + CHECK(door.editor->text() == std::string("yyx one\n two\n")); door.editor->undo(); - ensure_equals("as one step", door.editor->text(), std::string("yyxtimer one\ntimer two\n")); + UNSCOPED_INFO("as one step"); + CHECK(door.editor->text() == std::string("yyxtimer one\ntimer two\n")); } - template <> - template <> - void alscriptsearchpane_object::test<4>() + TEST_CASE_METHOD(alscriptsearchpane_data, "ALScriptSearchPane: Replace All asks then edits open scripts and opens the rest unsaved", "[ALScriptSearchPane]") { - set_test_name("Replace All, asked first: an open script's places as one step, a script or a notecard not open opened with the change unsaved"); ALScriptSearchPane& out = make(); Doc& door = doc("door", "llSay(0, \"timer\");\n"); const ALScriptRef away(fresh(), fresh()); @@ -340,7 +363,8 @@ namespace tut find("timer", "object"); out.fetched(studio.fetches[0].generation, "Thing", away, "away.lsl", std::make_shared("timer\n"), false); out.fetched(studio.fetches[1].generation, "Thing", card, "card", std::make_shared("a timer\n"), true); - ensure_equals("three found", out.search().found().size(), size_t(3)); + UNSCOPED_INFO("three found"); + CHECK(out.search().found().size() == size_t(3)); services.whenOpened = [this, card](const ALScriptRef& ref, const std::string&) { Doc& opened = ref == card ? doc("card", "a timer\n", true) : doc("away", "timer\n"); opened.ref = ref; @@ -348,19 +372,36 @@ namespace tut window.find("search_replacement")->setText(std::string("clock")); out.askReplaceAll(); - ensure_equals("asked", studio.asked.size(), size_t(1)); - ensure_equals("of the scripts and the notecard", studio.asked[0]["SCRIPTS"].asString(), services.counted("ScriptsNotecards", 3)); - ensure_equals("the open one, replaced", door.editor->text(), std::string("llSay(0, \"clock\");\n")); - ensure_equals("the two not open, opened", services.opened.size(), size_t(2)); - ensure("by their items", services.opened[0].ref == away && services.opened[1].ref == card); + UNSCOPED_INFO("asked"); + REQUIRE(studio.asked.size() == size_t(1)); + UNSCOPED_INFO("of the scripts and the notecard"); + CHECK(studio.asked[0]["SCRIPTS"].asString() == services.counted("ScriptsNotecards", 3)); + UNSCOPED_INFO("the open one, replaced"); + CHECK(door.editor->text() == std::string("llSay(0, \"clock\");\n")); + UNSCOPED_INFO("the two not open, opened"); + REQUIRE(services.opened.size() == size_t(2)); + { + INFO("by their items"); + CHECK(services.opened[0].ref == away); + CHECK(services.opened[1].ref == card); + } const Doc* opened = services.findDoc(away); - ensure("a tab for it", opened != nullptr); - ensure_equals("its change waiting on its text", opened->pendingEdits.size(), size_t(1)); - ensure_equals("which it replaces", opened->pendingEdits[0].now, std::string("clock")); + UNSCOPED_INFO("a tab for it"); + REQUIRE(opened != nullptr); + UNSCOPED_INFO("its change waiting on its text"); + REQUIRE(opened->pendingEdits.size() == size_t(1)); + UNSCOPED_INFO("which it replaces"); + CHECK(opened->pendingEdits[0].now == std::string("clock")); const Doc* notecard = services.findDoc(card); - ensure("the notecard's too", notecard && notecard->pendingEdits.size() == 1 && notecard->pendingEdits[0].now == "clock"); - ensure_equals("and each is made", studio.applied.size(), size_t(2)); - ensure_equals("said", services.reports.back().text, + { + INFO("the notecard's too"); + REQUIRE(notecard); + REQUIRE(notecard->pendingEdits.size() == 1); + CHECK(notecard->pendingEdits[0].now == "clock"); + } + UNSCOPED_INFO("and each is made"); + CHECK(studio.applied.size() == size_t(2)); + CHECK(services.reports.back().text == services.words("SearchReplaced", { { "[PLACES]", services.counted("Places", 3) }, { "[SCRIPTS]", services.counted("ScriptsNotecards", 3) } }) + "; " + services.words("SearchReplacedOpened", @@ -368,11 +409,8 @@ namespace tut "."); } - template <> - template <> - void alscriptsearchpane_object::test<5>() + TEST_CASE_METHOD(alscriptsearchpane_data, "ALScriptSearchPane: Replace All's step for a found script follows where it is now", "[ALScriptSearchPane]") { - set_test_name("what Replace All does with a script found, by where it is now"); using Step = ALScriptSearch::Step; using At = ALScriptSearch::Now::At; ALScriptSearch::Found one; @@ -382,49 +420,58 @@ namespace tut ALTextDocument text("timer\n"); ALScriptSearch::Now now; - ensure("closed, with no text kept: left", ALScriptSearch::step(one, now) == Step::Leave); + UNSCOPED_INFO("closed, with no text kept: left"); + CHECK(ALScriptSearch::step(one, now) == Step::Leave); one.text = "timer\n"; - ensure("closed, with its text: opened", ALScriptSearch::step(one, now) == Step::Open); + UNSCOPED_INFO("closed, with its text: opened"); + CHECK(ALScriptSearch::step(one, now) == Step::Open); ALScriptSearch::Found file = one; file.ref = ALScriptRef(); - ensure("a file closed since: left", ALScriptSearch::step(file, now) == Step::Leave); + UNSCOPED_INFO("a file closed since: left"); + CHECK(ALScriptSearch::step(file, now) == Step::Leave); now.at = At::Here; now.loaded = true; now.modifiable = true; now.text = &text; - ensure("here, at another version: left", ALScriptSearch::step(one, now) == Step::Leave); + UNSCOPED_INFO("here, at another version: left"); + CHECK(ALScriptSearch::step(one, now) == Step::Leave); one.version = text.version(); - ensure("here, as searched: replaced", ALScriptSearch::step(one, now) == Step::Replace); + UNSCOPED_INFO("here, as searched: replaced"); + CHECK(ALScriptSearch::step(one, now) == Step::Replace); now.modifiable = false; - ensure("here, not to be changed: left", ALScriptSearch::step(one, now) == Step::Leave); + UNSCOPED_INFO("here, not to be changed: left"); + CHECK(ALScriptSearch::step(one, now) == Step::Leave); now.modifiable = true; now.plainText = true; - ensure("here, a text file: skipped", ALScriptSearch::step(one, now) == Step::Skip); + UNSCOPED_INFO("here, a text file: skipped"); + CHECK(ALScriptSearch::step(one, now) == Step::Skip); now.plainText = false; now.at = At::Elsewhere; - ensure("elsewhere, reading as it did: replaced", ALScriptSearch::step(one, now) == Step::Replace); + UNSCOPED_INFO("elsewhere, reading as it did: replaced"); + CHECK(ALScriptSearch::step(one, now) == Step::Replace); ALTextDocument typed("timer!\n"); now.text = &typed; - ensure("elsewhere, typed in since: left", ALScriptSearch::step(one, now) == Step::Leave); + UNSCOPED_INFO("elsewhere, typed in since: left"); + CHECK(ALScriptSearch::step(one, now) == Step::Leave); one.notecard = true; now.text = &text; - ensure("found in a notecard in the world: replaced as a script is", ALScriptSearch::step(one, now) == Step::Replace); + UNSCOPED_INFO("found in a notecard in the world: replaced as a script is"); + CHECK(ALScriptSearch::step(one, now) == Step::Replace); ALScriptSearch::Found text_file = one; text_file.ref = ALScriptRef(); - ensure("found in a text file: skipped", ALScriptSearch::step(text_file, now) == Step::Skip); + UNSCOPED_INFO("found in a text file: skipped"); + CHECK(ALScriptSearch::step(text_file, now) == Step::Skip); one.notecard = false; one.places.clear(); - ensure("nothing found: skipped", ALScriptSearch::step(one, now) == Step::Skip); + UNSCOPED_INFO("nothing found: skipped"); + CHECK(ALScriptSearch::step(one, now) == Step::Skip); } - template <> - template <> - void alscriptsearchpane_object::test<6>() + TEST_CASE_METHOD(alscriptsearchpane_data, "ALScriptSearchPane: included files are searched once each on request and left by Replace All", "[ALScriptSearchPane]") { - set_test_name("what the scripts include searched too where asked: each file once, as its own, left alone by Replace All"); ALScriptSearchPane& out = make(); doc("door", "#include \"lib\"\ntimer\n"); doc("gate", "#include \"lib\"\n"); @@ -432,25 +479,36 @@ namespace tut studio.includes["door.lsl"] = lib; studio.includes["gate.lsl"] = lib; find("timer"); - ensure_equals("alone: the script's own", out.search().found().size(), size_t(1)); - ensure("nothing asked of what it includes", studio.askedIncludes.empty()); + UNSCOPED_INFO("alone: the script's own"); + CHECK(out.search().found().size() == size_t(1)); + UNSCOPED_INFO("nothing asked of what it includes"); + CHECK(studio.askedIncludes.empty()); out.bar()->setValue("includes", "yes"); const S32 before = studio.apart; find("timer"); - ensure_equals("and the file it includes, once for the two", out.search().found().size(), size_t(2)); - ensure("sought apart, once, and back", studio.apart == before + 1 && out.search().pending() == 0); + UNSCOPED_INFO("and the file it includes, once for the two"); + REQUIRE(out.search().found().size() == size_t(2)); + { + INFO("sought apart, once, and back"); + CHECK(studio.apart == before + 1); + CHECK(out.search().pending() == 0); + } const ALScriptSearch::Found& found = out.search().found()[1]; - ensure_equals("by its identity", found.file, std::string("disk:/scripts/lib.lsl")); - ensure("said to be included", found.where.find(services.words("SearchIncluded")) != std::string::npos && found.where.find("lib.lsl") != std::string::npos); - ensure_equals("a row for its place", out.list()->getItemCount(), 2); - ensure("Replace All leaves it", ALScriptSearch::step(found, ALScriptSearch::Now()) == ALScriptSearch::Step::Leave); + UNSCOPED_INFO("by its identity"); + CHECK(found.file == std::string("disk:/scripts/lib.lsl")); + { + INFO("said to be included"); + CHECK(found.where.find(services.words("SearchIncluded")) != std::string::npos); + CHECK(found.where.find("lib.lsl") != std::string::npos); + } + UNSCOPED_INFO("a row for its place"); + CHECK(out.list()->getItemCount() == 2); + UNSCOPED_INFO("Replace All leaves it"); + CHECK(ALScriptSearch::step(found, ALScriptSearch::Now()) == ALScriptSearch::Step::Leave); } - template <> - template <> - void alscriptsearchpane_object::test<7>() + TEST_CASE_METHOD(alscriptsearchpane_data, "ALScriptSearchPane: an object is searched once listed and the count says what was passed over", "[ALScriptSearchPane]") { - set_test_name("an object's scripts searched once its prims have said what they hold; the prims that did not, and a script not read, said with the count"); ALScriptSearchPane& out = make(); const ALScriptRef one(fresh(), fresh()); const ALScriptRef two(fresh(), fresh()); @@ -464,20 +522,26 @@ namespace tut studio.inHand = root; studio.holdListing = true; find("timer", "object"); - ensure("nothing fetched while its prims are asked", studio.fetches.empty()); - ensure_equals("waited for", out.search().pending(), 1); + UNSCOPED_INFO("nothing fetched while its prims are asked"); + CHECK(studio.fetches.empty()); + UNSCOPED_INFO("waited for"); + CHECK(out.search().pending() == 1); studio.heldListing(); - ensure_equals("both fetched", studio.fetches.size(), size_t(2)); + UNSCOPED_INFO("both fetched"); + REQUIRE(studio.fetches.size() == size_t(2)); out.fetched(studio.fetches[0].generation, "Thing", one, "one.lsl", std::make_shared("timer\n"), false); out.fetched(studio.fetches[1].generation, "Thing", two, "two.lsl", nullptr, false); - ensure_equals("none to wait for", out.search().pending(), 0); - ensure_equals("one read", out.search().found().size(), size_t(1)); + UNSCOPED_INFO("none to wait for"); + CHECK(out.search().pending() == 0); + UNSCOPED_INFO("one read"); + CHECK(out.search().found().size() == size_t(1)); const std::string said = services.words("SearchCount", { { "[HITS]", services.counted("Matches", 1) }, { "[FILES]", services.counted("Files", 1) }, { "[SHOWN]", "2000" } }); const std::string missed = services.words("SearchBoth", { { "[FIRST]", services.counted("SearchUnlisted", 3) }, { "[SECOND]", services.counted("SearchUnread", 1) } }); - ensure_equals("what was passed over, after what was found", counted(), + UNSCOPED_INFO("what was passed over, after what was found"); + CHECK(counted() == services.words("SearchPassedOver", { { "[SAID]", said }, { "[MISSED]", missed } })); // A search begun again lets the held listing go: its answer is an @@ -487,28 +551,41 @@ namespace tut std::function first = std::move(studio.heldListing); find("other", "object"); first(); - ensure("the earlier listing dropped", studio.fetches.empty()); + UNSCOPED_INFO("the earlier listing dropped"); + CHECK(studio.fetches.empty()); studio.heldListing(); - ensure_equals("the later one searched", studio.fetches.size(), size_t(2)); + UNSCOPED_INFO("the later one searched"); + CHECK(studio.fetches.size() == size_t(2)); } - template <> - template <> - void alscriptsearchpane_object::test<8>() + TEST_CASE_METHOD(alscriptsearchpane_data, "ALScriptSearchPane: Sources keeps a script by its text and drops the oldest past the limit", "[ALScriptSearchPane]") { - set_test_name("a script read for a search kept by which text it is: found again only for that text; no more than the most kept, the oldest going"); ALScriptSearch::Sources& sources = ALScriptSearch::Sources::instance(); sources.clear(); const ALScriptRef one(LLUUID::generateNewID(), LLUUID::generateNewID()); const LLUUID asset = LLUUID::generateNewID(); sources.keep(one, LLUUID::null, { std::make_shared("timer"), "one.lsl", false }); - ensure("which text unknown: not kept", sources.bytes() == 0); + UNSCOPED_INFO("which text unknown: not kept"); + CHECK(sources.bytes() == 0); sources.keep(one, asset, { std::make_shared("timer"), "one.lsl", false }); const ALScriptSearch::Sources::Source* kept = sources.find(one, asset); - ensure("found by its text", kept && *kept->text == "timer" && kept->name == "one.lsl"); - ensure("not for another", !sources.find(one, LLUUID::generateNewID()) && !sources.find(one, LLUUID::null)); + { + INFO("found by its text"); + REQUIRE(kept); + CHECK(*kept->text == "timer"); + CHECK(kept->name == "one.lsl"); + } + { + INFO("not for another"); + CHECK_FALSE(sources.find(one, LLUUID::generateNewID())); + CHECK_FALSE(sources.find(one, LLUUID::null)); + } sources.keep(one, LLUUID::generateNewID(), { std::make_shared("saved since"), "one.lsl", false }); - ensure("saved since: in its place", !sources.find(one, asset) && sources.bytes() == std::string("saved since").size()); + { + INFO("saved since: in its place"); + CHECK_FALSE(sources.find(one, asset)); + CHECK(sources.bytes() == std::string("saved since").size()); + } sources.clear(); const size_t big = ALScriptSearch::Sources::MOST / 3 + 1; @@ -520,53 +597,74 @@ namespace tut assets.push_back(LLUUID::generateNewID()); if (i == 2) { - ensure("the first used again", sources.find(refs[0], assets[0]) != nullptr); + UNSCOPED_INFO("the first used again"); + REQUIRE(sources.find(refs[0], assets[0]) != nullptr); } sources.keep(refs[i], assets[i], { std::make_shared(big, 'x'), "big", false }); } - ensure("within the most", sources.bytes() <= ALScriptSearch::Sources::MOST); - ensure("the one used longest ago gone", !sources.find(refs[1], assets[1]) && sources.find(refs[0], assets[0]) && sources.find(refs[2], assets[2])); + UNSCOPED_INFO("within the most"); + CHECK(sources.bytes() <= ALScriptSearch::Sources::MOST); + { + INFO("the one used longest ago gone"); + CHECK_FALSE(sources.find(refs[1], assets[1])); + CHECK(sources.find(refs[0], assets[0])); + CHECK(sources.find(refs[2], assets[2])); + } sources.clear(); } - template <> - template <> - void alscriptsearchpane_object::test<9>() + TEST_CASE_METHOD(alscriptsearchpane_data, "ALScriptSearchPane: a fetched script is sought apart and an earlier search's is dropped", "[ALScriptSearchPane]") { - set_test_name("a fetched script sought apart, still to come until the answer is back; one of an earlier search dropped"); ALScriptSearchPane& out = make(); const ALScriptRef away(LLUUID::generateNewID(), LLUUID::generateNewID()); studio.objects = { { LLUUID::generateNewID(), "Thing", { away } } }; find("timer", "listed"); - ensure_equals("fetched", studio.fetches.size(), size_t(1)); + REQUIRE(studio.fetches.size() == size_t(1)); out.fetched(studio.fetches[0].generation, "Thing", away, "away.lsl", std::make_shared("timer\n"), false); - ensure("sought apart", studio.apart == 1 && out.search().found().size() == 1 && out.search().pending() == 0); + { + INFO("sought apart"); + CHECK(studio.apart == 1); + CHECK(out.search().found().size() == 1); + CHECK(out.search().pending() == 0); + } const S32 before = studio.apart; out.fetched(studio.fetches[0].generation + 7, "Thing", away, "away.lsl", std::make_shared("timer\n"), false); - ensure("another search's: not sought", studio.apart == before); + UNSCOPED_INFO("another search's: not sought"); + CHECK(studio.apart == before); } - template <> - template <> - void alscriptsearchpane_object::test<10>() + TEST_CASE_METHOD(alscriptsearchpane_data, "ALScriptSearchPane: inventory search reads open scripts as they stand and fetches the rest", "[ALScriptSearchPane]") { - set_test_name("the inventory searched: a script open here as it stands, the rest fetched; typing in an inventory script searches it again"); ALScriptSearchPane& out = make(); Doc& door = doc("door", "timer here\n"); door.ref = ALScriptRef(LLUUID::null, fresh()); const ALScriptRef away(LLUUID::null, fresh()); studio.inventory = { { door.ref, "door" }, { away, "away.lsl" } }; find("timer", "inventory"); - ensure("the open one as it stands", out.search().found().size() == 1 && out.search().found()[0].doc == door.id); - ensure_equals("the other fetched", studio.fetches.size(), size_t(1)); - ensure("from the inventory", studio.fetches[0].ref == away && studio.fetches[0].where == window.services().words("SearchInventoryWhere")); + { + INFO("the open one as it stands"); + REQUIRE(out.search().found().size() == 1); + CHECK(out.search().found()[0].doc == door.id); + } + UNSCOPED_INFO("the other fetched"); + REQUIRE(studio.fetches.size() == size_t(1)); + { + INFO("from the inventory"); + CHECK(studio.fetches[0].ref == away); + CHECK(studio.fetches[0].where == window.services().words("SearchInventoryWhere")); + } out.fetched(studio.fetches[0].generation, studio.fetches[0].where, away, "away.lsl", std::make_shared("a timer\n"), false); - ensure("both found", out.search().found().size() == 2 && out.search().pending() == 0); + { + INFO("both found"); + REQUIRE(out.search().found().size() == 2); + CHECK(out.search().pending() == 0); + } door.editor->setCaret(ALTextPos(0, 0)); door.editor->insertText("x"); out.typedIn(door); std::this_thread::sleep_for(std::chrono::milliseconds(700)); out.pump(); - ensure_equals("typed in: searched again", out.search().found()[0].places[0].begin.column, 1); + UNSCOPED_INFO("typed in: searched again"); + CHECK(out.search().found()[0].places[0].begin.column == 1); } } diff --git a/indra/newview/tests/alscriptsnippets_test.cpp b/indra/newview/tests/alscriptsnippets_test.cpp index a16945a00a..5fde82db5a 100644 --- a/indra/newview/tests/alscriptsnippets_test.cpp +++ b/indra/newview/tests/alscriptsnippets_test.cpp @@ -33,7 +33,7 @@ #include "llfile.h" #include "lluuid.h" -#include "../test/lltut.h" +#include #include #include @@ -44,7 +44,7 @@ #include #endif -namespace tut +namespace { struct alscriptsnippets_data { @@ -103,27 +103,28 @@ namespace tut } }; - typedef test_group alscriptsnippets_group; - typedef alscriptsnippets_group::object alscriptsnippets_object; - alscriptsnippets_group alscriptsnippets_instance("alscriptsnippets"); - - template<> template<> - void alscriptsnippets_object::test<1>() + TEST_CASE_METHOD(alscriptsnippets_data, "alscriptsnippets: snippets read back as written and one with no body yet stays out", "[alscriptsnippets]") { - set_test_name("written and read back; one being written, with no body yet, stays out of the file"); const std::string file = folder + "/lsl.xml"; std::vector none; - ensure("no file is no snippets, and nothing wrong", ALScriptSnippets::readFrom(file, false, none) && none.empty()); - ensure("written", ALScriptSnippets::writeTo(file, { snippet("say", "llSay(0, \"$1\");"), snippet("half", "") })); + { + INFO("no file is no snippets, and nothing wrong"); + CHECK(ALScriptSnippets::readFrom(file, false, none)); + REQUIRE(none.empty()); + } + CHECK(ALScriptSnippets::writeTo(file, { snippet("say", "llSay(0, \"$1\");"), snippet("half", "") })); std::vector back; - ensure("read", ALScriptSnippets::readFrom(file, false, back)); - ensure("the one with a body", back.size() == 1 && back[0].name == "say" && back[0].body == "llSay(0, \"$1\");"); + REQUIRE(ALScriptSnippets::readFrom(file, false, back)); + { + INFO("the one with a body"); + REQUIRE(back.size() == 1); + CHECK(back[0].name == "say"); + CHECK(back[0].body == "llSay(0, \"$1\");"); + } } - template<> template<> - void alscriptsnippets_object::test<2>() + TEST_CASE_METHOD(alscriptsnippets_data, "alscriptsnippets: a file that does not read is kept beside and not written over", "[alscriptsnippets]") { - set_test_name("a file that does not read as snippets is kept beside, not written over"); const std::string file = folder + "/lsl.xml"; const std::string broken = "namemine "; { @@ -131,32 +132,47 @@ namespace tut out << broken; } std::vector held; - ensure("it does not read", !ALScriptSnippets::readFrom(file, false, held)); - ensure("written all the same", ALScriptSnippets::writeTo(file, { snippet("say", "llSay(0, \"hi\");") })); - ensure("what it held kept, as it was", LLFile::isfile(file + ".unreadable") && read(file + ".unreadable") == broken); + CHECK_FALSE(ALScriptSnippets::readFrom(file, false, held)); + UNSCOPED_INFO("written all the same"); + CHECK(ALScriptSnippets::writeTo(file, { snippet("say", "llSay(0, \"hi\");") })); + { + INFO("what it held kept, as it was"); + CHECK(LLFile::isfile(file + ".unreadable")); + CHECK(read(file + ".unreadable") == broken); + } std::vector back; - ensure("the new file reads", ALScriptSnippets::readFrom(file, false, back) && back.size() == 1); + { + INFO("the new file reads"); + CHECK(ALScriptSnippets::readFrom(file, false, back)); + REQUIRE(back.size() == 1); + } // Broken again: kept beside the first, which is not written over. { llofstream out(file, std::ios::binary); out << "again"; } - ensure("written", ALScriptSnippets::writeTo(file, { snippet("say", "x") })); - ensure("the first kept still", read(file + ".unreadable") == broken); - ensure("the second beside it", read(file + ".unreadable.1") == "again"); + CHECK(ALScriptSnippets::writeTo(file, { snippet("say", "x") })); + UNSCOPED_INFO("the first kept still"); + CHECK(read(file + ".unreadable") == broken); + UNSCOPED_INFO("the second beside it"); + CHECK(read(file + ".unreadable.1") == "again"); } - template<> template<> - void alscriptsnippets_object::test<3>() + TEST_CASE_METHOD(alscriptsnippets_data, "alscriptsnippets: a write replaces the file whole and a failed one leaves it as it was", "[alscriptsnippets]") { - set_test_name("written whole in the file's place: nothing is left beside it, and a write that fails leaves the file as it was"); const std::string file = folder + "/lsl.xml"; - ensure("written", ALScriptSnippets::writeTo(file, { snippet("say", "llSay(0, \"one\");") })); + CHECK(ALScriptSnippets::writeTo(file, { snippet("say", "llSay(0, \"one\");") })); const std::string first = read(file); - ensure("written again, over it", ALScriptSnippets::writeTo(file, { snippet("say", "llSay(0, \"two\");") })); - ensure("replaced", read(file) != first && read(file).find("two") != std::string::npos); - ensure("and nothing half-written beside it", !LLFile::isfile(ALFileWrite::besideOf(file))); + UNSCOPED_INFO("written again, over it"); + CHECK(ALScriptSnippets::writeTo(file, { snippet("say", "llSay(0, \"two\");") })); + { + INFO("replaced"); + CHECK(read(file) != first); + CHECK(read(file).find("two") != std::string::npos); + } + UNSCOPED_INFO("and nothing half-written beside it"); + CHECK_FALSE(LLFile::isfile(ALFileWrite::besideOf(file))); // A file that cannot be written is not touched: never opened for // writing and left cut short. Root writes what it likes. @@ -169,18 +185,18 @@ namespace tut const std::string written = read(file); std::error_code ec; std::filesystem::permissions(fsyspath(file), std::filesystem::perms::owner_read, std::filesystem::perm_options::replace, ec); - ensure("a file nothing can be written in", !ec); + UNSCOPED_INFO("a file nothing can be written in"); + REQUIRE_FALSE(ec); const bool wrote = ALScriptSnippets::writeTo(file, { snippet("say", "llSay(0, \"three\");") }); std::filesystem::permissions(fsyspath(file), std::filesystem::perms::owner_read | std::filesystem::perms::owner_write, std::filesystem::perm_options::replace, ec); - ensure("not written", !wrote); - ensure("and the file as it was", read(file) == written); + CHECK_FALSE(wrote); + UNSCOPED_INFO("and the file as it was"); + CHECK(read(file) == written); } - template<> template<> - void alscriptsnippets_object::test<4>() + TEST_CASE_METHOD(alscriptsnippets_data, "alscriptsnippets: a notecard carries both languages and passes over keys it does not know", "[alscriptsnippets]") { - set_test_name("a notecard carries both languages and reads back into each; keys it does not know are passed over"); ALScriptSnippets::Snippet say = snippet("say", "llSay(0, \"${1:hello}\");"); say.prefix = "say"; say.detail = "Say something"; @@ -189,15 +205,17 @@ namespace tut const std::string text = ALScriptSnippets::notecardText({ say, builtin, snippet("", "no name") }, { snippet("print", "print(${1})") }); std::vector lsl; std::vector slua; - ensure("read", ALScriptSnippets::readNotecard(text, false, lsl, slua)); - ensure_equals("the LSL one, and not the viewer's or the unnamed", lsl.size(), 1U); - ensure_equals("its name", lsl[0].name, std::string("say")); - ensure_equals("its prefix", lsl[0].prefix, std::string("say")); - ensure_equals("its detail", lsl[0].detail, std::string("Say something")); - ensure_equals("its body", lsl[0].body, say.body); - ensure("not the viewer's", !lsl[0].builtin); - ensure_equals("the SLua one in SLua's", slua.size(), 1U); - ensure_equals("its body", slua[0].body, std::string("print(${1})")); + REQUIRE(ALScriptSnippets::readNotecard(text, false, lsl, slua)); + UNSCOPED_INFO("the LSL one, and not the viewer's or the unnamed"); + REQUIRE(lsl.size() == 1U); + CHECK(lsl[0].name == std::string("say")); + CHECK(lsl[0].prefix == std::string("say")); + CHECK(lsl[0].detail == std::string("Say something")); + CHECK(lsl[0].body == say.body); + CHECK_FALSE(lsl[0].builtin); + UNSCOPED_INFO("the SLua one in SLua's"); + REQUIRE(slua.size() == 1U); + CHECK(slua[0].body == std::string("print(${1})")); // Written by hand or by an older viewer: no language, and a key // this one does not know. @@ -205,105 +223,151 @@ namespace tut "colourred"; lsl.clear(); slua.clear(); - ensure("read", ALScriptSnippets::readNotecard(plain, true, lsl, slua)); - ensure("into the language shown", lsl.empty() && slua.size() == 1 && slua[0].name == "loop"); + CHECK(ALScriptSnippets::readNotecard(plain, true, lsl, slua)); + { + INFO("into the language shown"); + REQUIRE(lsl.empty()); + REQUIRE(slua.size() == 1); + CHECK(slua[0].name == "loop"); + } } - template<> template<> - void alscriptsnippets_object::test<5>() + TEST_CASE_METHOD(alscriptsnippets_data, "alscriptsnippets: a notecard that is not snippets reads as nothing and says so", "[alscriptsnippets]") { - set_test_name("a notecard that is not snippets reads as nothing, and says so"); std::vector lsl; std::vector slua; for (const char* text : { "Dear diary, today I scripted a door.", "namex", "titlex", "", "" }) { - ensure(std::string("not snippets: ") + text, !ALScriptSnippets::readNotecard(text, false, lsl, slua)); - ensure("and nothing added", lsl.empty() && slua.empty()); + CAPTURE(text); + REQUIRE_FALSE(ALScriptSnippets::readNotecard(text, false, lsl, slua)); + { + INFO("and nothing added"); + REQUIRE(lsl.empty()); + REQUIRE(slua.empty()); + } } } - template<> template<> - void alscriptsnippets_object::test<6>() + TEST_CASE_METHOD(alscriptsnippets_data, "alscriptsnippets: merge leaves out the same snippet and numbers one whose name is taken", "[alscriptsnippets]") { - set_test_name("brought in beside one's own: the same left out, a name taken by another body numbered, and again left out"); std::vector own = { snippet("say", "llSay(0, \"a\");"), snippet("loop", "for") }; const std::vector incoming = { snippet("say", "llSay(0, \"b\");"), snippet("loop", "for"), snippet("new", "x") }; ALScriptSnippets::Merged merged = ALScriptSnippets::merge(own, incoming); - ensure_equals("one added under its own name", merged.added, 1U); - ensure_equals("one under a number", merged.renamed, 1U); - ensure_equals("one the same", merged.skipped, 1U); - ensure_equals("four now", own.size(), 4U); - ensure_equals("numbered", own[2].name, std::string("say (2)")); - ensure_equals("with its body", own[2].body, std::string("llSay(0, \"b\");")); - ensure_equals("and the new one", own[3].name, std::string("new")); + UNSCOPED_INFO("one added under its own name"); + CHECK(merged.added == 1U); + UNSCOPED_INFO("one under a number"); + CHECK(merged.renamed == 1U); + UNSCOPED_INFO("one the same"); + CHECK(merged.skipped == 1U); + REQUIRE(own.size() == 4U); + CHECK(own[2].name == std::string("say (2)")); + CHECK(own[2].body == std::string("llSay(0, \"b\");")); + CHECK(own[3].name == std::string("new")); // The same notecard again brings nothing in. merged = ALScriptSnippets::merge(own, incoming); - ensure("nothing again", merged.added == 0 && merged.renamed == 0 && merged.skipped == 3); - ensure_equals("still four", own.size(), 4U); + { + INFO("nothing again"); + CHECK(merged.added == 0); + CHECK(merged.renamed == 0); + CHECK(merged.skipped == 3); + } + REQUIRE(own.size() == 4U); // A third body takes the next number free. merged = ALScriptSnippets::merge(own, { snippet("say", "llSay(0, \"c\");") }); - ensure_equals("numbered past the taken", own.back().name, std::string("say (3)")); + UNSCOPED_INFO("numbered past the taken"); + CHECK(own.back().name == std::string("say (3)")); } - template<> template<> - void alscriptsnippets_object::test<7>() + TEST_CASE_METHOD(alscriptsnippets_data, "alscriptsnippets: a followed notecard's snippets go to their languages and are never saved", "[alscriptsnippets]") { - set_test_name("a followed notecard's snippets: each in its language, one saying none in both, taken in place of those before; text that is not snippets taken as nothing; never written as one's own"); const std::string text = "" "namesaybodyllSay(0, \"\");languagelsl" "nameloopbodyforlanguageslua" "namebannerbodyx" ""; - ensure("followed", ALScriptSnippets::follow(text)); + CHECK(ALScriptSnippets::follow(text)); const std::vector& lsl = ALScriptSnippets::followed(false); const std::vector& slua = ALScriptSnippets::followed(true); - ensure("LSL's and the one saying none", lsl.size() == 2 && lsl[0].name == "say" && lsl[1].name == "banner"); - ensure("SLua's and the one saying none", slua.size() == 2 && slua[0].name == "loop" && slua[1].name == "banner"); - ensure("each the notecard's", lsl[0].followed && lsl[1].followed && slua[0].followed && !lsl[0].builtin); + { + INFO("LSL's and the one saying none"); + REQUIRE(lsl.size() == 2); + CHECK(lsl[0].name == "say"); + CHECK(lsl[1].name == "banner"); + } + { + INFO("SLua's and the one saying none"); + REQUIRE(slua.size() == 2); + CHECK(slua[0].name == "loop"); + CHECK(slua[1].name == "banner"); + } + { + INFO("each the notecard's"); + CHECK(lsl[0].followed); + CHECK(lsl[1].followed); + CHECK(slua[0].followed); + CHECK_FALSE(lsl[0].builtin); + } - ensure("not snippets: not taken", !ALScriptSnippets::follow("Dear diary")); - ensure("those before stay", ALScriptSnippets::followed(false).size() == 2); + UNSCOPED_INFO("not snippets: not taken"); + CHECK_FALSE(ALScriptSnippets::follow("Dear diary")); + UNSCOPED_INFO("those before stay"); + REQUIRE(ALScriptSnippets::followed(false).size() == 2); // Never written where the scripter's own are, nor carried by their // notecard; copied in, one's own. const std::string file = folder + "/lsl.xml"; - ensure("written", ALScriptSnippets::writeTo(file, { snippet("mine", "y"), lsl[0] })); + CHECK(ALScriptSnippets::writeTo(file, { snippet("mine", "y"), lsl[0] })); std::vector back; - ensure("read", ALScriptSnippets::readFrom(file, false, back)); - ensure("one's own alone", back.size() == 1 && back[0].name == "mine"); - ensure("not on one's notecard", ALScriptSnippets::notecardText(lsl, {}).find("banner") == std::string::npos); + REQUIRE(ALScriptSnippets::readFrom(file, false, back)); + { + INFO("one's own alone"); + REQUIRE(back.size() == 1); + CHECK(back[0].name == "mine"); + } + UNSCOPED_INFO("not on one's notecard"); + CHECK(ALScriptSnippets::notecardText(lsl, {}).find("banner") == std::string::npos); std::vector own; ALScriptSnippets::merge(own, { lsl[1] }); - ensure("copied in: one's own", own.size() == 1 && !own[0].followed); + { + INFO("copied in: one's own"); + REQUIRE(own.size() == 1); + CHECK_FALSE(own[0].followed); + } - ensure("none followed", ALScriptSnippets::follow(std::string())); - ensure("none left", ALScriptSnippets::followed(false).empty() && ALScriptSnippets::followed(true).empty()); + UNSCOPED_INFO("none followed"); + CHECK(ALScriptSnippets::follow(std::string())); + { + INFO("none left"); + REQUIRE(ALScriptSnippets::followed(false).empty()); + REQUIRE(ALScriptSnippets::followed(true).empty()); + } } - template<> template<> - void alscriptsnippets_object::test<8>() + TEST_CASE_METHOD(alscriptsnippets_data, "alscriptsnippets: every shipped snippet expands with whole stops and no placeholder left", "[alscriptsnippets]") { - set_test_name("every snippet the viewer ships expands whole: each stop over all of what its placeholder holds, no bracket of it left " - "outside, and nothing of a placeholder left in the text"); for (const char* language : { "lsl.xml", "slua.xml" }) { const std::string file = std::string(LLUI_TEST_APP_DIR) + "/app_settings/snippets/" + language; + CAPTURE(file); std::vector shipped; - ensure(std::string("read: ") + file, ALScriptSnippets::readFrom(file, true, shipped) && !shipped.empty()); + REQUIRE(ALScriptSnippets::readFrom(file, true, shipped)); + REQUIRE_FALSE(shipped.empty()); for (const ALScriptSnippets::Snippet& one : shipped) { - const std::string named = std::string(language) + ", " + one.name; + CAPTURE(one.name); const ALSnippetSession::Expansion expanded = ALSnippetSession::expand(one.body, ALTextPos(0, 0), std::string()); const ALTextDocument text(expanded.text); - ensure(named + ": no placeholder left in " + expanded.text, expanded.text.find("${") == std::string::npos); + UNSCOPED_INFO("no placeholder left in " << expanded.text); + REQUIRE(expanded.text.find("${") == std::string::npos); for (size_t i = 0; i < expanded.stops.size(); ++i) { const std::string held = text.text(expanded.stops[i]); - ensure(named + ": stop " + std::to_string(i + 1) + " holds the whole of its text, not \"" + held + "\"", whole(held)); + UNSCOPED_INFO("stop " << i + 1 << " holds the whole of its text, not \"" << held << "\""); + REQUIRE(whole(held)); } } } diff --git a/indra/newview/tests/alscriptstudio_fixture.h b/indra/newview/tests/alscriptstudio_fixture.h index bf3a330679..f3d2190e2f 100644 --- a/indra/newview/tests/alscriptstudio_fixture.h +++ b/indra/newview/tests/alscriptstudio_fixture.h @@ -57,6 +57,7 @@ #include "../../llui/tests/alheadlessui_fixture.h" #include "alscriptstudio_stubs.h" +#include "newview_test_fakes.h" #include #include @@ -461,6 +462,7 @@ namespace al_studio_test unit(services, given ? *given : tabs, *this) { std::filesystem::create_directories(fsyspath(folder)); + newview_test::useRecoveryWriter(); store = std::make_unique(folder, "this-session"); ALRecovery::useStore(store.get()); } diff --git a/indra/newview/tests/alscriptstudioaccount_test.cpp b/indra/newview/tests/alscriptstudioaccount_test.cpp index 06d4bec17e..ac0b930104 100644 --- a/indra/newview/tests/alscriptstudioaccount_test.cpp +++ b/indra/newview/tests/alscriptstudioaccount_test.cpp @@ -27,21 +27,14 @@ #include "../alscriptstudioaccount.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { - struct alscriptstudioaccount_data + // The account's own is taken out of a state written whole, the shape and switches left shared; + // a key it no longer has is taken out of the account's too. + TEST_CASE("alscriptstudioaccount: split takes the account's own out of the state and drops what it lost", "[alscriptstudioaccount]") { - }; - typedef test_group alscriptstudioaccount_group; - typedef alscriptstudioaccount_group::object alscriptstudioaccount_object; - alscriptstudioaccount_group alscriptstudioaccount_instance("alscriptstudioaccount"); - - template<> template<> - void alscriptstudioaccount_object::test<1>() - { - set_test_name("the account's own taken out of a state written whole, the shape and switches left shared; a key it no longer has taken out of the account's too"); LLSD state; state["rect"] = LLSD().with(0, 1).with(1, 2); state["open"]["tabs"] = LLSD::emptyArray(); @@ -51,27 +44,49 @@ namespace tut LLSD mine; mine["windows"] = LLSD().with(0, "w"); ALScriptStudioAccount::split(state, mine); - ensure("out of the shared", !state.has("open") && !state.has("pinned") && !state.has("recent_scripts")); - ensure("the shape, the switches and the files left", state.has("rect") && state.has("recent_files")); - ensure("into the account's", mine.has("open") && mine["pinned"][0].asString() == "a" && mine["recent_scripts"][0].asString() == "s"); - ensure("what the state no longer has, gone from the account's", !mine.has("windows")); + { + INFO("out of the shared"); + REQUIRE_FALSE(state.has("open")); + REQUIRE_FALSE(state.has("pinned")); + REQUIRE_FALSE(state.has("recent_scripts")); + } + { + INFO("the shape, the switches and the files left"); + REQUIRE(state.has("rect")); + REQUIRE(state.has("recent_files")); + } + { + INFO("into the account's"); + REQUIRE(mine.has("open")); + CHECK(mine["pinned"][0].asString() == "a"); + CHECK(mine["recent_scripts"][0].asString() == "s"); + } + UNSCOPED_INFO("what the state no longer has, gone from the account's"); + REQUIRE_FALSE(mine.has("windows")); } - template<> template<> - void alscriptstudioaccount_object::test<2>() + // Read: the account's own over the shared where it has kept any, a key it has not taken out; + // the shared as it was where it has kept none. + TEST_CASE("alscriptstudioaccount: merged lays the account's own over the shared once it has kept any", "[alscriptstudioaccount]") { - set_test_name("read: the account's own over the shared where it has kept any, a key it has not taken out; the shared as it was where it has kept none"); LLSD shared; shared["rect"] = 1; shared["open"] = "old tabs"; shared["pinned"] = "old pins"; const LLSD first = ALScriptStudioAccount::merged(shared, LLSD()); - ensure("none kept yet: the shared taken over", first["open"].asString() == "old tabs" && first["pinned"].asString() == "old pins"); + { + INFO("none kept yet: the shared taken over"); + CHECK(first["open"].asString() == "old tabs"); + CHECK(first["pinned"].asString() == "old pins"); + } LLSD mine; mine["open"] = "my tabs"; const LLSD read = ALScriptStudioAccount::merged(shared, mine); - ensure("its own", read["open"].asString() == "my tabs"); - ensure("another account's pins not seen", !read.has("pinned")); - ensure("the shape shared", read["rect"].asInteger() == 1); + UNSCOPED_INFO("its own"); + CHECK(read["open"].asString() == "my tabs"); + UNSCOPED_INFO("another account's pins not seen"); + REQUIRE_FALSE(read.has("pinned")); + UNSCOPED_INFO("the shape shared"); + CHECK(read["rect"].asInteger() == 1); } } diff --git a/indra/newview/tests/alscriptstudiocaret_test.cpp b/indra/newview/tests/alscriptstudiocaret_test.cpp index d2cf37b7db..0c8de212b1 100644 --- a/indra/newview/tests/alscriptstudiocaret_test.cpp +++ b/indra/newview/tests/alscriptstudiocaret_test.cpp @@ -32,7 +32,7 @@ #include "alscriptstudio_fixture.h" #include "llfocusmgr.h" -#include "../test/lltut.h" +#include #include @@ -134,7 +134,7 @@ namespace const std::string SCRIPT = "integer count;\ndefault { state_entry() { count = 1; llSay(0, \"x\"); } }\n"; } -namespace tut +namespace { struct alscriptstudiocaret_data { @@ -166,7 +166,7 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } navigation = std::make_unique(services); lookup = std::make_unique(services, navigation->unit); @@ -199,50 +199,56 @@ namespace tut } }; - typedef test_group alscriptstudiocaret_group; - typedef alscriptstudiocaret_group::object alscriptstudiocaret_object; - alscriptstudiocaret_group alscriptstudiocaret_instance("alscriptstudiocaret"); - - template<> template<> - void alscriptstudiocaret_object::test<1>() + TEST_CASE_METHOD(alscriptstudiocaret_data, "alscriptstudiocaret: go to definition jumps or says there is none and drops stale answers", "[alscriptstudiocaret]") { - set_test_name("a definition asked for: gone to in the script, said where there is none, and an answer to another question dropped"); ALScriptStudioCaret& caret = make(); Doc& doc = tab("a"); const U32 v = ask(doc, ALEditorCommand::GoToDefinition); - ensure_equals("asked", joined(studio.asked), std::string("references 0:8")); - ensure("remembered", doc.caret->symbolCommand == ALEditorCommand::GoToDefinition && doc.caret->symbolVersion == v); + CHECK(joined(studio.asked) == std::string("references 0:8")); + { + INFO("remembered"); + CHECK(doc.caret->symbolCommand == ALEditorCommand::GoToDefinition); + CHECK(doc.caret->symbolVersion == v); + } caret.answered(doc, references(v + 1), ALTextPos(0, 8)); caret.answered(doc, references(v), ALTextPos(0, 9)); - ensure("of another text or place: nothing", studio.said.empty()); + UNSCOPED_INFO("of another text or place: nothing"); + REQUIRE(studio.said.empty()); caret.answered(doc, references(v), ALTextPos(0, 8)); - ensure_equals("gone", joined(studio.said), std::string("go a 0:8-0:13")); - ensure_equals("a jump noted, for Back", navigation->unit.places(false), size_t(1)); + CHECK(joined(studio.said) == std::string("go a 0:8-0:13")); + UNSCOPED_INFO("a jump noted, for Back"); + CHECK(navigation->unit.places(false) == size_t(1)); caret.answered(doc, references(v), ALTextPos(0, 8)); - ensure("answered once", studio.said.size() == 1 && navigation->unit.places(false) == 1); + { + INFO("answered once"); + REQUIRE(studio.said.size() == 1); + CHECK(navigation->unit.places(false) == 1); + } ask(doc, ALEditorCommand::GoToDefinition); caret.answered(doc, references(v, false), ALTextPos(0, 8)); - ensure("none: said, no jump", studio.said.size() == 1 && navigation->unit.places(false) == 1 && - services.statuses.back().find("NoDefinition [NAME]=count") == 0); + { + INFO("none: said, no jump"); + REQUIRE(studio.said.size() == 1); + CHECK(navigation->unit.places(false) == 1); + CHECK(services.statuses.back().find("NoDefinition [NAME]=count") == 0); + } ask(doc, ALEditorCommand::GoToDefinition); ALScriptAnalysis::Result none; none.version = v; none.understood = true; caret.answered(doc, none, ALTextPos(0, 8)); - ensure_equals("nothing known of it", services.statuses.back(), std::string("NothingKnown [NAME]=count")); - ensure("not a failure", !services.statusFailures.back()); + CHECK(services.statuses.back() == std::string("NothingKnown [NAME]=count")); + CHECK_FALSE(services.statusFailures.back()); ask(doc, ALEditorCommand::FindReferences); none.understood = false; caret.answered(doc, none, ALTextPos(0, 8)); - ensure_equals("nothing read", services.statuses.back(), std::string("NothingKnownBroken [NAME]=count")); - ensure("a failure", services.statusFailures.back()); + CHECK(services.statuses.back() == std::string("NothingKnownBroken [NAME]=count")); + CHECK(services.statusFailures.back()); } - template<> template<> - void alscriptstudiocaret_object::test<2>() + TEST_CASE_METHOD(alscriptstudiocaret_data, "alscriptstudiocaret: a language word shows its reference unless the script defines it", "[alscriptstudiocaret]") { - set_test_name("a word of the language with no definition of the script's: its reference; one the script defines, gone to"); ALScriptStudioCaret& caret = make(); Doc& doc = tab("a"); const U32 v = doc.editor->document().version(); @@ -251,21 +257,24 @@ namespace tut result.version = v; result.understood = true; caret.answered(doc, result, ALTextPos(1, 37)); - ensure_equals("the reference", joined(studio.said), std::string("reference llSay")); + CHECK(joined(studio.said) == std::string("reference llSay")); caret.ask(doc, ALEditorCommand::FindReferences, ALTextRange(ALTextPos(1, 37), ALTextPos(1, 42))); caret.answered(doc, result, ALTextPos(1, 37)); - ensure("finding it: not", studio.said.size() == 1 && services.statuses.back().find("NothingKnown") == 0); + { + INFO("finding it: not"); + REQUIRE(studio.said.size() == 1); + CHECK(services.statuses.back().find("NothingKnown") == 0); + } caret.ask(doc, ALEditorCommand::GoToDefinition, ALTextRange(ALTextPos(1, 37), ALTextPos(1, 42))); ALScriptAnalysis::Result own = references(v); own.references.name = "llSay"; caret.answered(doc, own, ALTextPos(1, 37)); - ensure_equals("the script's own", studio.said.back(), std::string("go a 0:8-0:13")); + UNSCOPED_INFO("the script's own"); + CHECK(studio.said.back() == std::string("go a 0:8-0:13")); } - template<> template<> - void alscriptstudiocaret_object::test<3>() + TEST_CASE_METHOD(alscriptstudiocaret_data, "alscriptstudiocaret: through the expansion an include's definition and places map back", "[alscriptstudiocaret]") { - set_test_name("through the expansion: declared in an include, opened there; each place read back, in the script or the include"); ALScriptStudioCaret& caret = make(); Doc& doc = tab("a"); studio.expanded = true; @@ -289,85 +298,131 @@ namespace tut doc.expanded.version = v; ALScriptAnalysis::Result result = references(v); caret.answered(doc, result, ALTextPos(0, 8)); - ensure_equals("opened in the include", joined(studio.said), std::string("open disk:/inc.lsl inc.lsl 4:8+5")); - ensure_equals("a jump noted", navigation->unit.places(false), size_t(1)); + CHECK(joined(studio.said) == std::string("open disk:/inc.lsl inc.lsl 4:8+5")); + UNSCOPED_INFO("a jump noted"); + CHECK(navigation->unit.places(false) == size_t(1)); ask(doc, ALEditorCommand::FindReferences); result.references.references = { span(0, 8, 5), span(2, 26, 5), span(7, 0, 5) }; caret.answered(doc, result, ALTextPos(0, 8)); - ensure_equals("the lookup started", started(doc), "find in a declared disk:/inc.lsl@4 v" + std::to_string(v)); - ensure_equals("the places that map", doc.lookup->places.size(), size_t(2)); + CHECK(started(doc) == "find in a declared disk:/inc.lsl@4 v" + std::to_string(v)); + UNSCOPED_INFO("the places that map"); + REQUIRE(doc.lookup->places.size() == size_t(2)); const Doc::Place* included = placeIn(doc, "disk:/inc.lsl"); - ensure("the include's, as held", included && included->fileName == "inc.lsl" && included->text == "integer count; // held" && included->at == 6); + { + INFO("the include's, as held"); + REQUIRE(included); + CHECK(included->fileName == "inc.lsl"); + CHECK(included->text == "integer count; // held"); + CHECK(included->at == 6); + } const Doc::Place* own = placeIn(doc, std::string()); - ensure("the script's own, as typed", own && own->span.line == 1 && own->at == 26); + { + INFO("the script's own, as typed"); + REQUIRE(own); + CHECK(own->span.line == 1); + CHECK(own->at == 26); + } studio.held.clear(); ask(doc, ALEditorCommand::Rename); caret.answered(doc, result, ALTextPos(0, 8)); included = placeIn(doc, "disk:/inc.lsl"); - ensure("the include not held: the expansion's line", included && included->text == "integer count;" && included->at == -1); - ensure_equals("where the include has it", included->span.line, 4); - ensure("to rename", started(doc).find("rename in a") == 0); + { + INFO("the include not held: the expansion's line"); + REQUIRE(included); + CHECK(included->text == "integer count;"); + CHECK(included->at == -1); + } + UNSCOPED_INFO("where the include has it"); + CHECK(included->span.line == 4); + CHECK(started(doc).find("rename in a") == 0); doc.expanded.version = v + 7; ask(doc, ALEditorCommand::FindReferences); caret.answered(doc, result, ALTextPos(0, 8)); - ensure_equals("an expansion of another text: not read through", started(doc), "find in a declared @0 v" + std::to_string(v)); - ensure("its places as they are", doc.lookup->places.size() == 3 && placeIn(doc, std::string(), 7)); + UNSCOPED_INFO("an expansion of another text: not read through"); + CHECK(started(doc) == "find in a declared @0 v" + std::to_string(v)); + { + INFO("its places as they are"); + REQUIRE(doc.lookup->places.size() == 3); + REQUIRE(placeIn(doc, std::string(), 7)); + } doc.expanded.version = v; ask(doc, ALEditorCommand::GoToDefinition); result.references.definition = span(7, 0, 5); const size_t said = studio.said.size(); caret.answered(doc, result, ALTextPos(0, 8)); - ensure("declared where no source is: none", studio.said.size() == said && services.statuses.back().find("NoDefinition") == 0); + { + INFO("declared where no source is: none"); + REQUIRE(studio.said.size() == said); + CHECK(services.statuses.back().find("NoDefinition") == 0); + } } - template<> template<> - void alscriptstudiocaret_object::test<4>() + TEST_CASE_METHOD(alscriptstudiocaret_data, "alscriptstudiocaret: the watched caret tells its path and asks the inspector once settled", "[alscriptstudiocaret]") { - set_test_name("the caret watched: the path told as it moves, the lit places put out once it leaves them, the inspector once it settles"); ALScriptStudioCaret& caret = make(); caret.pump(10.0); Doc& doc = tab("a"); doc.editor->setCaret(ALTextPos(0, 10)); doc.editor->setHighlights(ALCodeEditor::Highlight::References, { ALTextRange(ALTextPos(0, 8), ALTextPos(0, 13)), ALTextRange(ALTextPos(1, 26), ALTextPos(1, 31)) }); caret.pump(10.0); - ensure_equals("told", joined(studio.said), std::string("path 0:10")); - ensure("the places kept while it is on one", doc.editor->highlights().size() == 2); + CHECK(joined(studio.said) == std::string("path 0:10")); + UNSCOPED_INFO("the places kept while it is on one"); + REQUIRE(doc.editor->highlights().size() == 2); caret.pump(10.2); - ensure("not yet settled", studio.asked.empty() && studio.said.size() == 1); + { + INFO("not yet settled"); + REQUIRE(studio.asked.empty()); + REQUIRE(studio.said.size() == 1); + } caret.pump(10.0 + ALScriptStudioCaret::SETTLE); - ensure_equals("settled: asked", joined(studio.asked), std::string("inspect 0:8")); - ensure("remembered", doc.caret->inspectAt == ALTextPos(0, 8) && doc.caret->inspectVersion == doc.editor->document().version()); + UNSCOPED_INFO("settled: asked"); + CHECK(joined(studio.asked) == std::string("inspect 0:8")); + { + INFO("remembered"); + CHECK(doc.caret->inspectAt == ALTextPos(0, 8)); + CHECK(doc.caret->inspectVersion == doc.editor->document().version()); + } doc.editor->setCaret(ALTextPos(0, 12)); caret.pump(20.0); caret.pump(21.0); - ensure("the same name: not again", studio.asked.size() == 1 && studio.said.size() == 2); + { + INFO("the same name: not again"); + REQUIRE(studio.asked.size() == 1); + REQUIRE(studio.said.size() == 2); + } doc.editor->insertText("s"); caret.pump(30.0); caret.pump(31.0); - ensure_equals("typed in: again", studio.asked.size(), size_t(2)); + UNSCOPED_INFO("typed in: again"); + REQUIRE(studio.asked.size() == size_t(2)); doc.editor->setCaret(ALTextPos(1, 9)); caret.pump(40.0); - ensure("left the lit places: put out", doc.editor->highlights().empty()); + UNSCOPED_INFO("left the lit places: put out"); + REQUIRE(doc.editor->highlights().empty()); caret.pump(41.0); - ensure("on no name: the problems", studio.said.back() == "problems 1:9" && doc.caret->inspectAt == ALTextPos(0, 8)); + { + INFO("on no name: the problems"); + CHECK(studio.said.back() == "problems 1:9"); + CHECK(doc.caret->inspectAt == ALTextPos(0, 8)); + } const size_t said = studio.said.size(); caret.pump(42.0); - ensure_equals("told once, not every frame", studio.said.size(), said); + UNSCOPED_INFO("told once, not every frame"); + REQUIRE(studio.said.size() == said); studio.problems = true; doc.editor->setCaret(ALTextPos(1, 8)); caret.pump(50.0); caret.pump(51.0); - ensure("some shown: the name forgotten", doc.caret->inspectAt == ALTextPos(-1, -1)); + UNSCOPED_INFO("some shown: the name forgotten"); + CHECK(doc.caret->inspectAt == ALTextPos(-1, -1)); } - template<> template<> - void alscriptstudiocaret_object::test<5>() + TEST_CASE_METHOD(alscriptstudiocaret_data, "alscriptstudiocaret: unloaded tabs and notecards are skipped and the expansion not inspected", "[alscriptstudiocaret]") { - set_test_name("not watched with no tab, one not loaded or a notecard; the expansion in front told nothing; the keyboard in the text told"); ALScriptStudioCaret& caret = make(); Doc& doc = tab("a"); doc.loaded = false; @@ -375,47 +430,53 @@ namespace tut doc.loaded = true; doc.notecard = true; caret.pump(1.0); - ensure("nothing", studio.said.empty()); + REQUIRE(studio.said.empty()); doc.notecard = false; doc.expandedEditor = editor("expanded_a", "integer count;\n"); doc.view = Doc::View::Expanded; doc.expandedEditor->setCaret(ALTextPos(0, 3)); caret.pump(1.0); caret.pump(9.0); - ensure_equals("the expansion's caret, the path told", joined(studio.said), std::string("path 0:0")); - ensure("the inspector not", studio.asked.empty() && doc.caret->inspectDue == 0.0); + UNSCOPED_INFO("the expansion's caret, the path told"); + CHECK(joined(studio.said) == std::string("path 0:0")); + { + INFO("the inspector not"); + REQUIRE(studio.asked.empty()); + CHECK(doc.caret->inspectDue == 0.0); + } navigation->unit.noteJump(true); caret.pump(9.2); - ensure("a walk down a list goes on while the text has no keyboard", navigation->unit.walking()); + UNSCOPED_INFO("a walk down a list goes on while the text has no keyboard"); + CHECK(navigation->unit.walking()); doc.expandedEditor->setFocus(true); caret.pump(9.5); - ensure("and ends once it has", !navigation->unit.walking()); + UNSCOPED_INFO("and ends once it has"); + CHECK_FALSE(navigation->unit.walking()); } - template<> template<> - void alscriptstudiocaret_object::test<6>() + TEST_CASE_METHOD(alscriptstudiocaret_data, "alscriptstudiocaret: a folded inspector is asked nothing until it is brought out", "[alscriptstudiocaret]") { - set_test_name("with the inspector folded away nothing is asked as the caret settles; brought out, what is at the caret is asked"); ALScriptStudioCaret& caret = make(); Doc& doc = tab("a"); studio.inspector = false; doc.editor->setCaret(ALTextPos(0, 10)); caret.pump(10.0); caret.pump(10.0 + ALScriptStudioCaret::SETTLE); - ensure("folded: nothing asked", studio.asked.empty()); + UNSCOPED_INFO("folded: nothing asked"); + REQUIRE(studio.asked.empty()); caret.pump(20.0); - ensure("still nothing", studio.asked.empty()); + UNSCOPED_INFO("still nothing"); + REQUIRE(studio.asked.empty()); studio.inspector = true; caret.pump(21.0); - ensure_equals("brought out: asked", joined(studio.asked), std::string("inspect 0:8")); + UNSCOPED_INFO("brought out: asked"); + CHECK(joined(studio.asked) == std::string("inspect 0:8")); caret.pump(22.0); - ensure_equals("once", studio.asked.size(), size_t(1)); + REQUIRE(studio.asked.size() == size_t(1)); } - template<> template<> - void alscriptstudiocaret_object::test<7>() + TEST_CASE_METHOD(alscriptstudiocaret_data, "alscriptstudiocaret: the caret's path changes on a new symbol or a new outline", "[alscriptstudiocaret]") { - set_test_name("the caret's path found once for both panes: changed where it moves into another symbol, or the outline is made again"); ALScriptStudioCaret& caret = make(); Doc& doc = tab("a"); ALScriptOutlineEntry state; @@ -429,52 +490,74 @@ namespace tut doc.outline = { state, event }; doc.editor->setCaret(ALTextPos(1, 12)); caret.placePath(doc); - ensure("in both", doc.caret->crumbPath == std::vector({ 0, 1 }) && studio.said.back() == "path 1:12 changed"); + { + INFO("in both"); + CHECK(doc.caret->crumbPath == std::vector({ 0, 1 })); + CHECK(studio.said.back() == "path 1:12 changed"); + } doc.editor->setCaret(ALTextPos(1, 14)); caret.placePath(doc); - ensure_equals("the same symbols: shown, not changed", studio.said.back(), std::string("path 1:14")); + UNSCOPED_INFO("the same symbols: shown, not changed"); + CHECK(studio.said.back() == std::string("path 1:14")); doc.editor->setCaret(ALTextPos(1, 2)); caret.placePath(doc); - ensure("out of the event: changed", doc.caret->crumbPath == std::vector({ 0 }) && studio.said.back() == "path 1:2 changed"); + { + INFO("out of the event: changed"); + CHECK(doc.caret->crumbPath == std::vector({ 0 })); + CHECK(studio.said.back() == "path 1:2 changed"); + } ++doc.check->analysisVersion; caret.placePath(doc); - ensure_equals("a new outline: changed", studio.said.back(), std::string("path 1:2 changed")); + UNSCOPED_INFO("a new outline: changed"); + CHECK(studio.said.back() == std::string("path 1:2 changed")); doc.expandedEditor = editor("expanded_a", "integer count;\n"); doc.view = Doc::View::Expanded; caret.placePath(doc); - ensure("the expansion in front: no path", doc.caret->crumbPath.empty()); + UNSCOPED_INFO("the expansion in front: no path"); + REQUIRE(doc.caret->crumbPath.empty()); } - template<> template<> - void alscriptstudiocaret_object::test<8>() + TEST_CASE_METHOD(alscriptstudiocaret_data, "alscriptstudiocaret: seeAfresh and inspectAfresh and inspect tell the window again", "[alscriptstudiocaret]") { - set_test_name("the caret as the window asks: seen afresh and told again where it has not moved; the inspector told again, or now"); ALScriptStudioCaret& caret = make(); Doc& doc = tab("a"); doc.editor->setCaret(ALTextPos(0, 10)); caret.pump(10.0); caret.pump(10.0 + ALScriptStudioCaret::SETTLE); - ensure("told, and asked", studio.said.size() == 1 && studio.asked.size() == 1); + { + INFO("told, and asked"); + REQUIRE(studio.said.size() == 1); + REQUIRE(studio.asked.size() == 1); + } caret.pump(20.0); - ensure("not again where it stands", studio.said.size() == 1); + UNSCOPED_INFO("not again where it stands"); + REQUIRE(studio.said.size() == 1); ALScriptStudioCaret::seeAfresh(doc); caret.pump(30.0); caret.pump(30.0 + ALScriptStudioCaret::SETTLE); - ensure("seen afresh: told again, the inspector not, of the same word", studio.said.size() == 2 && studio.asked.size() == 1); + { + INFO("seen afresh: told again, the inspector not, of the same word"); + REQUIRE(studio.said.size() == 2); + REQUIRE(studio.asked.size() == 1); + } ALScriptStudioCaret::seeAfresh(doc); ALScriptStudioCaret::inspectAfresh(doc); caret.pump(40.0); caret.pump(40.0 + ALScriptStudioCaret::SETTLE); - ensure_equals("the inspector told again", joined(studio.asked), std::string("inspect 0:8, inspect 0:8")); + UNSCOPED_INFO("the inspector told again"); + CHECK(joined(studio.asked) == std::string("inspect 0:8, inspect 0:8")); caret.inspect(doc, ALTextPos(1, 26)); - ensure_equals("told now of a word", studio.asked.back(), std::string("inspect 1:26")); - ensure("which is what it was last told", doc.caret->inspectAt == ALTextPos(1, 26) && doc.caret->inspectDue == 0.0); + UNSCOPED_INFO("told now of a word"); + CHECK(studio.asked.back() == std::string("inspect 1:26")); + { + INFO("which is what it was last told"); + CHECK(doc.caret->inspectAt == ALTextPos(1, 26)); + CHECK(doc.caret->inspectDue == 0.0); + } } - template<> template<> - void alscriptstudiocaret_object::test<9>() + TEST_CASE_METHOD(alscriptstudiocaret_data, "alscriptstudiocaret: a definition in a required module opens through the module's map", "[alscriptstudiocaret]") { - set_test_name("declared in a module the script requires, which the analyzers read apart: opened there, through the module's own map"); ALScriptStudioCaret& caret = make(); Doc& doc = tab("a"); studio.expanded = true; @@ -507,25 +590,32 @@ namespace tut result.references.definitionFile = "disk:/lib/util.luau"; result.references.references = { span(1, 8, 5) }; caret.answered(doc, result, ALTextPos(0, 8)); - ensure_equals("opened in the module", joined(studio.said), std::string("open disk:/lib/util.luau util.luau 3:11+5")); - ensure_equals("a jump noted", navigation->unit.places(false), size_t(1)); + CHECK(joined(studio.said) == std::string("open disk:/lib/util.luau util.luau 3:11+5")); + UNSCOPED_INFO("a jump noted"); + CHECK(navigation->unit.places(false) == size_t(1)); ask(doc, ALEditorCommand::GoToDefinition); result.references.definitionFile = "disk:/lib/gone.luau"; caret.answered(doc, result, ALTextPos(0, 8)); - ensure("a module the expansion has not: none", studio.said.size() == 1 && services.statuses.back().find("NoDefinition") == 0); + { + INFO("a module the expansion has not: none"); + REQUIRE(studio.said.size() == 1); + CHECK(services.statuses.back().find("NoDefinition") == 0); + } ask(doc, ALEditorCommand::GoToDefinition); result.references.definitionFile = "disk:/lib/util.luau"; doc.expanded.version = v + 3; caret.answered(doc, result, ALTextPos(0, 8)); - ensure("an expansion of another text: none", studio.said.size() == 1 && services.statuses.back().find("NoDefinition") == 0); + { + INFO("an expansion of another text: none"); + REQUIRE(studio.said.size() == 1); + CHECK(services.statuses.back().find("NoDefinition") == 0); + } } - template<> template<> - void alscriptstudiocaret_object::test<10>() + TEST_CASE_METHOD(alscriptstudiocaret_data, "alscriptstudiocaret: a name's places in required modules map back to each module's file", "[alscriptstudiocaret]") { - set_test_name("a name's places in the modules the script requires, read apart: each back in its file, as the file reads where it is held, else as the module's text"); ALScriptStudioCaret& caret = make(); Doc& doc = tab("a"); studio.expanded = true; @@ -568,18 +658,35 @@ namespace tut result.references.references = { span(1, 26, 5) }; result.references.elsewhere = { { "held", span(4, 8, 5) }, { "other", span(1, 12, 5) }, { "gone", span(0, 0, 5) } }; caret.answered(doc, result, ALTextPos(0, 8)); - ensure_equals("declared in the held module", started(doc), "find in a declared disk:/held.luau@4 v" + std::to_string(v)); - ensure_equals("the script's, each module's; none of a module not had", doc.lookup->places.size(), size_t(3)); + CHECK(started(doc) == "find in a declared disk:/held.luau@4 v" + std::to_string(v)); + UNSCOPED_INFO("the script's, each module's; none of a module not had"); + REQUIRE(doc.lookup->places.size() == size_t(3)); const Doc::Place* held = placeIn(doc, "disk:/held.luau"); - ensure("as the file reads", held && held->fileName == "held.luau" && held->text == "integer count; // held" && held->at == 6); + { + INFO("as the file reads"); + REQUIRE(held); + CHECK(held->fileName == "held.luau"); + CHECK(held->text == "integer count; // held"); + CHECK(held->at == 6); + } const Doc::Place* other = placeIn(doc, "disk:/other.luau"); - ensure("as the module's text read, the name not placed", other && other->fileName == "other.luau" && - other->text == "return util.count" && other->at == -1); - ensure("in the module's lines", other->span.line == 1 && other->span.column == 12); + { + INFO("as the module's text read, the name not placed"); + REQUIRE(other); + CHECK(other->fileName == "other.luau"); + CHECK(other->text == "return util.count"); + CHECK(other->at == -1); + } + { + INFO("in the module's lines"); + CHECK(other->span.line == 1); + CHECK(other->span.column == 12); + } doc.expanded.version = v + 1; ask(doc, ALEditorCommand::FindReferences); caret.answered(doc, result, ALTextPos(0, 8)); - ensure_equals("an expansion of another text: the script's alone", doc.lookup->places.size(), size_t(1)); + UNSCOPED_INFO("an expansion of another text: the script's alone"); + REQUIRE(doc.lookup->places.size() == size_t(1)); } } diff --git a/indra/newview/tests/alscriptstudiochecking_test.cpp b/indra/newview/tests/alscriptstudiochecking_test.cpp index 3f07e83499..69f0babf54 100644 --- a/indra/newview/tests/alscriptstudiochecking_test.cpp +++ b/indra/newview/tests/alscriptstudiochecking_test.cpp @@ -40,7 +40,7 @@ #include "alscriptstudio_fixture.h" #include "llfocusmgr.h" -#include "../test/lltut.h" +#include #include #include @@ -231,7 +231,7 @@ namespace }; } -namespace tut +namespace { struct alscriptstudiochecking_data { @@ -286,7 +286,7 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } weighing = std::make_unique(services); unit = std::make_unique(services, studio, studio, weighing->unit, studio); @@ -356,38 +356,51 @@ namespace tut } }; - typedef test_group alscriptstudiochecking_group; - typedef alscriptstudiochecking_group::object alscriptstudiochecking_object; - alscriptstudiochecking_group alscriptstudiochecking_instance("alscriptstudiochecking"); - - template<> template<> - void alscriptstudiochecking_object::test<1>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: a check waits for typing to rest then asks of the text as it stands", "[alscriptstudiochecking]") { - set_test_name("a check waits while typing goes on and is asked once it rests, of the text as it stands, with the window's settings"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); checking.schedule(doc); const F64 due = doc.check->analysisDue; - ensure("due a moment on", due > 1.0); + UNSCOPED_INFO("due a moment on"); + CHECK(due > 1.0); checking.pump(due - 0.1); - ensure("not yet", studio.asks.empty()); + UNSCOPED_INFO("not yet"); + REQUIRE(studio.asks.empty()); doc.editor->insertText("x"); checking.schedule(doc); checking.pump(due); - ensure("typed again: waits again", studio.asks.empty() && doc.check->analysisDue > due); + { + INFO("typed again: waits again"); + REQUIRE(studio.asks.empty()); + CHECK(doc.check->analysisDue > due); + } checking.pump(doc.check->analysisDue); - ensure_equals("asked once", studio.asks.size(), size_t(1)); + UNSCOPED_INFO("asked once"); + REQUIRE(studio.asks.size() == size_t(1)); const ALScriptAnalysis::Request& asked = studio.asks[0].request; - ensure("a check of the text as it stands", - asked.kind == Kind::Check && asked.id == "a" && asked.version == version(doc) && *asked.text == doc.editor->text()); - ensure("with the window's settings", asked.hintTypes); - ensure("remembered", doc.check->requestedVersion == version(doc) && doc.check->analysisDue == 0.0); + { + INFO("a check of the text as it stands"); + CHECK(asked.kind == Kind::Check); + CHECK(asked.id == "a"); + CHECK(asked.version == version(doc)); + CHECK(*asked.text == doc.editor->text()); + } + UNSCOPED_INFO("with the window's settings"); + CHECK(asked.hintTypes); + { + INFO("remembered"); + CHECK(doc.check->requestedVersion == version(doc)); + CHECK(doc.check->analysisDue == 0.0); + } checking.pump(1e9); - ensure_equals("once", studio.asks.size(), size_t(1)); + UNSCOPED_INFO("once"); + REQUIRE(studio.asks.size() == size_t(1)); checking.schedule(doc, true); checking.pump(1.0); - ensure_equals("at once", studio.asks.size(), size_t(2)); - // A notecard is checked here, as scripts read it (test 20). + UNSCOPED_INFO("at once"); + REQUIRE(studio.asks.size() == size_t(2)); + // A notecard is checked here, as scripts read it (the notecard case below). doc.notecard = true; checking.schedule(doc, true); checking.pump(1e9); @@ -396,14 +409,18 @@ namespace tut checking.schedule(doc, true); doc.loaded = true; checking.pump(1e9); - ensure_equals("a notecard, or a tab not loaded: never asked of the analyzers", studio.asks.size(), size_t(2)); + UNSCOPED_INFO("a notecard, or a tab not loaded: never asked of the analyzers"); + REQUIRE(studio.asks.size() == size_t(2)); doc.notecard = true; checking.ask(doc, Kind::Hover, ALTextPos(0, 8), ALTextPos(0, 8)); - ensure_equals("a notecard not asked about", studio.asks.size(), size_t(2)); + UNSCOPED_INFO("a notecard not asked about"); + REQUIRE(studio.asks.size() == size_t(2)); preprocessing = true; - ensure("nor preprocessed", !checking.preprocessed(doc)); + UNSCOPED_INFO("nor preprocessed"); + CHECK_FALSE(checking.preprocessed(doc)); doc.notecard = false; - ensure("a script is", checking.preprocessed(doc)); + UNSCOPED_INFO("a script is"); + CHECK(checking.preprocessed(doc)); // A question of the text as it stands shares the one copy every // other question of that text holds; one of a text typed in since // has its own. @@ -412,17 +429,22 @@ namespace tut studio.asks.clear(); checking.ask(doc, Kind::Hover, ALTextPos(0, 1), ALTextPos(0, 1)); checking.ask(doc, Kind::Signature, ALTextPos(0, 1), ALTextPos(0, 1)); - ensure("the same text shared, not copied", studio.asks.size() == 2 && studio.asks[1].request.text == studio.asks[0].request.text); + { + INFO("the same text shared, not copied"); + REQUIRE(studio.asks.size() == 2); + CHECK(studio.asks[1].request.text == studio.asks[0].request.text); + } doc.editor->insertText("x"); checking.ask(doc, Kind::Hover, ALTextPos(0, 1), ALTextPos(0, 1)); - ensure("a newer text its own", studio.asks.back().request.text != studio.asks[0].request.text && - *studio.asks.back().request.text == doc.editor->text()); + { + INFO("a newer text its own"); + REQUIRE(studio.asks.back().request.text != studio.asks[0].request.text); + CHECK(*studio.asks.back().request.text == doc.editor->text()); + } } - template<> template<> - void alscriptstudiochecking_object::test<2>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: a check's answer is taken and one for a text since typed in dropped", "[alscriptstudiochecking]") { - set_test_name("a check's answer taken -- problems, outline, the window told -- one of a text since typed in dropped"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); checking.schedule(doc, true); @@ -433,50 +455,88 @@ namespace tut result.outline = { entry }; doc.editor->insertText("x"); studio.asks[0].answered(result); - ensure("of another text: dropped", doc.check->analysis.empty() && studio.told.empty()); + { + INFO("of another text: dropped"); + REQUIRE(doc.check->analysis.empty()); + REQUIRE(studio.told.empty()); + } result.version = version(doc); studio.asks[0].answered(result); - ensure("taken", doc.check->analysis.size() == 1 && doc.check->analysisVersion == version(doc) && doc.outline.size() == 1); - ensure_equals("LSL's lints as chosen", gLintsApplied, 1); - ensure_equals("told", joined(studio.told), std::string("problems a, outline a")); + { + INFO("taken"); + REQUIRE(doc.check->analysis.size() == 1); + CHECK(doc.check->analysisVersion == version(doc)); + REQUIRE(doc.outline.size() == 1); + } + UNSCOPED_INFO("LSL's lints as chosen"); + CHECK(gLintsApplied == 1); + UNSCOPED_INFO("told"); + CHECK(joined(studio.told) == std::string("problems a, outline a")); result.understood = false; result.outline.clear(); result.problems.clear(); studio.asks[0].answered(result); - ensure("past mending: the outline kept", doc.check->analysis.empty() && doc.outline.size() == 1); + { + INFO("past mending: the outline kept"); + REQUIRE(doc.check->analysis.empty()); + REQUIRE(doc.outline.size() == 1); + } doc.language.lua = true; studio.asks[0].answered(result); - ensure_equals("Luau's chosen by its configuration", gLintsApplied, 2); + UNSCOPED_INFO("Luau's chosen by its configuration"); + CHECK(gLintsApplied == 2); unit.reset(); result.problems = { problem(1, "late") }; studio.asks[0].answered(result); - ensure("gone: nothing taken", doc.check->analysis.empty()); + UNSCOPED_INFO("gone: nothing taken"); + REQUIRE(doc.check->analysis.empty()); } - template<> template<> - void alscriptstudiochecking_object::test<3>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: a preprocessed question waits on the expansion and maps back to source", "[alscriptstudiochecking]") { - set_test_name("preprocessed: the question waits on the expansion, one of a kind replacing the last; the answer read back to the source"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); preprocessing = true; checking.ask(doc, Kind::Inspect, ALTextPos(0, 8), ALTextPos(0, 8)); checking.ask(doc, Kind::Inspect, ALTextPos(3, 20), ALTextPos(3, 20)); checking.ask(doc, Kind::Check, ALTextPos(), ALTextPos()); - ensure("expanded once", expansions.size() == 1 && studio.asks.empty() && doc.check->waiting.size() == 2); - ensure("the last of a kind", doc.check->waiting[0].at == ALTextPos(3, 20)); + { + INFO("expanded once"); + REQUIRE(expansions.size() == 1); + REQUIRE(studio.asks.empty()); + REQUIRE(doc.check->waiting.size() == 2); + } + UNSCOPED_INFO("the last of a kind"); + CHECK(doc.check->waiting[0].at == ALTextPos(3, 20)); doc.editor->insertText("//"); expansions[0].second(expansion(SCRIPT)); - ensure("of another text: the questions go", doc.check->waiting.empty() && studio.asks.empty() && !doc.expanded.valid); + { + INFO("of another text: the questions go"); + REQUIRE(doc.check->waiting.empty()); + REQUIRE(studio.asks.empty()); + CHECK_FALSE(doc.expanded.valid); + } doc.editor->undoJournal().undo(); doc.editor->setText(SCRIPT); checking.ask(doc, Kind::Inspect, ALTextPos(3, 20), ALTextPos(3, 20)); expansions.back().second(expansion(SCRIPT)); - ensure("taken", doc.expanded.valid && doc.expanded.version == version(doc) && doc.check->expansions == 1); - ensure_equals("then asked", studio.asks.size(), size_t(1)); - ensure("of the expansion, at its place", *studio.asks[0].request.text == *doc.expanded.text && studio.asks[0].request.line == 4); - ensure("the include's line passed over", studio.asks[0].request.passedOver == (std::vector>{ { 0, 0 } })); - ensure_equals("the problems shown with it", joined(studio.told), std::string("problems a")); + { + INFO("taken"); + REQUIRE(doc.expanded.valid); + CHECK(doc.expanded.version == version(doc)); + CHECK(doc.check->expansions == 1); + } + UNSCOPED_INFO("then asked"); + REQUIRE(studio.asks.size() == size_t(1)); + { + INFO("of the expansion, at its place"); + CHECK(*studio.asks[0].request.text == *doc.expanded.text); + CHECK(studio.asks[0].request.line == 4); + } + UNSCOPED_INFO("the include's line passed over"); + CHECK(studio.asks[0].request.passedOver == (std::vector>{ { 0, 0 } })); + UNSCOPED_INFO("the problems shown with it"); + CHECK(joined(studio.told) == std::string("problems a")); ALScriptAnalysis::Result result; result.kind = Kind::Inspect; result.id = "a"; @@ -484,26 +544,33 @@ namespace tut result.line = 4; result.column = 20; studio.asks[0].answered(result); - ensure_equals("at the source's place", studio.told.back(), std::string("inspect a 3:20")); + UNSCOPED_INFO("at the source's place"); + CHECK(studio.told.back() == std::string("inspect a 3:20")); result.line = 0; const size_t told = studio.told.size(); studio.asks[0].answered(result); - ensure("in the include: nowhere of the script's", studio.told.size() == told); + UNSCOPED_INFO("in the include: nowhere of the script's"); + REQUIRE(studio.told.size() == told); checking.ask(doc, Kind::Inspect, ALTextPos(0, 0), ALTextPos(0, 0)); checking.ask(doc, Kind::Check, ALTextPos(), ALTextPos()); doc.check->expansions += 1; doc.expanded.generation += 1; studio.asks[2].answered(answer(doc)); - ensure("an answer of an expansion since replaced: the check asked again", doc.check->analysisDue == 1.0 && - doc.check->analysis.empty()); + { + INFO("an answer of an expansion since replaced: the check asked again"); + CHECK(doc.check->analysisDue == 1.0); + REQUIRE(doc.check->analysis.empty()); + } preprocessing = false; studio.asks[1].answered(result); - ensure("read plain now: dropped", studio.told.size() == told); + UNSCOPED_INFO("read plain now: dropped"); + REQUIRE(studio.told.size() == told); checking.ask(doc, Kind::Inspect, ALTextPos(3, 20), ALTextPos(3, 20)); preprocessing = true; result.line = 3; studio.asks.back().answered(result); - ensure("asked plain, read expanded now: dropped", studio.told.size() == told); + UNSCOPED_INFO("asked plain, read expanded now: dropped"); + REQUIRE(studio.told.size() == told); // An expansion of an older text is expanded again; a place a // directive holds is asked nothing; a stretch it carries as it @@ -511,12 +578,15 @@ namespace tut const size_t expanded = expansions.size(); doc.editor->insertText(" "); checking.ask(doc, Kind::Inspect, ALTextPos(3, 20), ALTextPos(3, 20)); - ensure("an older expansion: expanded again", expansions.size() == expanded + 1); + UNSCOPED_INFO("an older expansion: expanded again"); + REQUIRE(expansions.size() == expanded + 1); doc.editor->setText("#define X\n" + SCRIPT); checking.ask(doc, Kind::Check, ALTextPos(), ALTextPos()); - ensure("one still out: not another yet", expansions.size() == expanded + 1); + UNSCOPED_INFO("one still out: not another yet"); + REQUIRE(expansions.size() == expanded + 1); expansions.back().second(expansion(SCRIPT)); - ensure("it answered of an older text: the next, for the text as it is", expansions.size() == expanded + 2); + UNSCOPED_INFO("it answered of an older text: the next, for the text as it is"); + REQUIRE(expansions.size() == expanded + 2); ALPreprocessor::Result directive = expansion(SCRIPT); directive.map = ALSourceMap(); directive.map.addFile("a", "object:a"); @@ -533,10 +603,15 @@ namespace tut expansions.back().second(directive); const size_t asks = studio.asks.size(); checking.ask(doc, Kind::Hover, ALTextPos(0, 3), ALTextPos(0, 3)); - ensure("on the directive: nothing asked", studio.asks.size() == asks); + UNSCOPED_INFO("on the directive: nothing asked"); + REQUIRE(studio.asks.size() == asks); checking.ask(doc, Kind::Actions, ALTextPos(1, 8), ALTextPos(1, 13)); - ensure("a stretch kept", studio.asks.size() == asks + 1 && studio.asks.back().request.line == 0 && - studio.asks.back().request.endColumn == 13); + { + INFO("a stretch kept"); + REQUIRE(studio.asks.size() == asks + 1); + CHECK(studio.asks.back().request.line == 0); + CHECK(studio.asks.back().request.endColumn == 13); + } unit.reset(); doc.editor->insertText(" "); make(); @@ -545,13 +620,15 @@ namespace tut ALScriptStudioChecking* gone = unit.release(); delete gone; expansions.back().second(expansion(SCRIPT)); - ensure("gone before its expansion came: nothing", expansions.size() == stale_expansions + 1 && studio.asks.size() == asks + 1); + { + INFO("gone before its expansion came: nothing"); + REQUIRE(expansions.size() == stale_expansions + 1); + REQUIRE(studio.asks.size() == asks + 1); + } } - template<> template<> - void alscriptstudiochecking_object::test<4>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: an expansion's problems map back to the source or the include's file", "[alscriptstudiochecking]") { - set_test_name("a check of an expansion: problems back in the source, an include's kept by its file without fixes, its unused not said, one in generated code said so"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); preprocessing = true; @@ -578,52 +655,81 @@ namespace tut result.outline = { mine, theirs }; studio.asks[0].answered(result); const ALScriptProblems& got = doc.check->analysis; - ensure_equals("the include's unused dropped", got.size(), size_t(4)); - ensure("not an error of that code", got[2].message == "an error with that code"); - ensure("own: in the source, its fix too", got[0].line == 3 && got[0].file.empty() && got[0].fixes.size() == 1 && - got[0].fixes[0].edits[0].line == 3); - ensure("the include's: by its file, no fixes", got[1].file == "disk:/lib.lsl" && got[1].line == 2 && got[1].fixes.empty()); - ensure("nowhere: no fixes", got[3].fixes.empty()); - ensure("and said to be in code the preprocessor made, at the expansion's line", got[3].file == Doc::GENERATED && got[3].line == 9); - ensure("the include's symbol not outlined", doc.outline.size() == 1 && doc.outline[0].name == "count" && - doc.outline[0].nameSpan.line == 0); + UNSCOPED_INFO("the include's unused dropped"); + REQUIRE(got.size() == size_t(4)); + UNSCOPED_INFO("not an error of that code"); + CHECK(got[2].message == "an error with that code"); + { + INFO("own: in the source, its fix too"); + CHECK(got[0].line == 3); + REQUIRE(got[0].file.empty()); + REQUIRE(got[0].fixes.size() == 1); + CHECK(got[0].fixes[0].edits[0].line == 3); + } + { + INFO("the include's: by its file, no fixes"); + CHECK(got[1].file == "disk:/lib.lsl"); + CHECK(got[1].line == 2); + REQUIRE(got[1].fixes.empty()); + } + UNSCOPED_INFO("nowhere: no fixes"); + REQUIRE(got[3].fixes.empty()); + { + INFO("and said to be in code the preprocessor made, at the expansion's line"); + CHECK(got[3].file == Doc::GENERATED); + CHECK(got[3].line == 9); + } + { + INFO("the include's symbol not outlined"); + REQUIRE(doc.outline.size() == 1); + CHECK(doc.outline[0].name == "count"); + CHECK(doc.outline[0].nameSpan.line == 0); + } } - template<> template<> - void alscriptstudiochecking_object::test<5>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: an include's file checked with a state after it keeps only its problems", "[alscriptstudiochecking]") { - set_test_name("an include's file, checked with a state after it: what is said of the state, and of its unused, not the include's"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("disk:/lib.lsl", "integer helper;\nhelp() { }\n"); doc.file = "/lib.lsl"; doc.editor->setSyntax("lsl"); - ensure("a fragment", checking.lslFragment(doc)); - ensure("kept for the text it was asked of", doc.check->fragment && doc.check->fragment->first == doc.editor->document().version() && - doc.check->fragment->second); + CHECK(checking.lslFragment(doc)); + { + INFO("kept for the text it was asked of"); + REQUIRE(doc.check->fragment); + CHECK(doc.check->fragment->first == doc.editor->document().version()); + CHECK(doc.check->fragment->second); + } checking.ask(doc, Kind::Check, ALTextPos(), ALTextPos()); - ensure("a state put after it", *studio.asks[0].request.text == doc.editor->text() + "\ndefault{state_entry(){}}\n"); + UNSCOPED_INFO("a state put after it"); + CHECK(*studio.asks[0].request.text == doc.editor->text() + "\ndefault{state_entry(){}}\n"); ALScriptProblem unused = problem(0, "helper unused", ALScriptProblem::Severity::Warning); unused.code = "LocalUnused"; ALScriptOutlineEntry state; state.name = "default"; state.nameSpan.line = 4; studio.asks[0].answered(answer(doc, { problem(1, "own"), problem(4, "in the state"), unused })); - ensure("only its own", doc.check->analysis.size() == 1 && doc.check->analysis[0].message == "own"); + { + INFO("only its own"); + REQUIRE(doc.check->analysis.size() == 1); + CHECK(doc.check->analysis[0].message == "own"); + } ALScriptAnalysis::Result outlined = answer(doc); outlined.outline = { state }; studio.asks[0].answered(outlined); - ensure("nor the state outlined", doc.outline.empty()); + UNSCOPED_INFO("nor the state outlined"); + REQUIRE(doc.outline.empty()); doc.editor->setText("integer helper;\ndefault\n{\n}\n"); - ensure("with a state: a script", !checking.lslFragment(doc)); + UNSCOPED_INFO("with a state: a script"); + CHECK_FALSE(checking.lslFragment(doc)); doc.file.clear(); doc.editor->setText("integer helper;\n"); - ensure("not a file: a script", !checking.lslFragment(doc)); + UNSCOPED_INFO("not a file: a script"); + CHECK_FALSE(checking.lslFragment(doc)); } - template<> template<> - void alscriptstudiochecking_object::test<6>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: an undeclared name is offered an include or what is near is fetched", "[alscriptstudiochecking]") { - set_test_name("a name nothing declares offered an include that declares it; one nothing in hand gives fetched from what is near"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); preprocessing = true; @@ -648,42 +754,58 @@ namespace tut result.problems[1].line = 4; studio.asks[0].answered(result); const ALScriptProblem& given = doc.check->analysis[0]; - ensure_equals("offered", given.fixes.size(), size_t(1)); - ensure("the include put in, preferred", given.fixes[0].preferred && - given.fixes[0].edits[0].text.find("#include \"lib.lsl\"") != std::string::npos); - ensure("nothing gives the other", doc.check->analysis[1].fixes.empty()); - ensure_equals("what is near fetched", nearby.size(), size_t(1)); + UNSCOPED_INFO("offered"); + REQUIRE(given.fixes.size() == size_t(1)); + { + INFO("the include put in, preferred"); + CHECK(given.fixes[0].preferred); + REQUIRE(given.fixes[0].edits[0].text.find("#include \"lib.lsl\"") != std::string::npos); + } + UNSCOPED_INFO("nothing gives the other"); + REQUIRE(doc.check->analysis[1].fixes.empty()); + UNSCOPED_INFO("what is near fetched"); + REQUIRE(nearby.size() == size_t(1)); const size_t asks = studio.asks.size(); nearby[0](); checking.pump(1.0); - ensure_equals("and checked again once it is in", studio.asks.size(), asks + 1); + UNSCOPED_INFO("and checked again once it is in"); + REQUIRE(studio.asks.size() == asks + 1); preprocessing = false; doc.check->analysisDue = 0.0; checking.ask(doc, Kind::Check, ALTextPos(), ALTextPos()); studio.asks.back().answered(answer(doc, { undeclared })); - ensure("not preprocessed: nothing to include with", doc.check->analysis[0].fixes.empty() && nearby.size() == 1); + { + INFO("not preprocessed: nothing to include with"); + REQUIRE(doc.check->analysis[0].fixes.empty()); + REQUIRE(nearby.size() == 1); + } preprocessing = true; checking.ask(doc, Kind::Check, ALTextPos(), ALTextPos()); ALScriptAnalysis::Result given_all = answer(doc, { undeclared }); given_all.problems[0].line = 4; studio.asks.back().answered(given_all); - ensure("all given: nothing fetched", !doc.check->analysis[0].fixes.empty() && nearby.size() == 1); + { + INFO("all given: nothing fetched"); + REQUIRE_FALSE(doc.check->analysis[0].fixes.empty()); + REQUIRE(nearby.size() == 1); + } // What is in reach found anew on the index's thread: the script // checked again. doc.check->analysisDue = 0.0; - ensure("a reach new to it told", (bool)modulesReady); + UNSCOPED_INFO("a reach new to it told"); + REQUIRE((bool)modulesReady); modulesReady(); - ensure("checked again", doc.check->analysisDue > 0.0); + UNSCOPED_INFO("checked again"); + CHECK(doc.check->analysisDue > 0.0); doc.check->analysisDue = 0.0; unit.reset(); nearby[0](); - ensure("gone: nothing asked", doc.check->analysisDue == 0.0); + UNSCOPED_INFO("gone: nothing asked"); + CHECK(doc.check->analysisDue == 0.0); } - template<> template<> - void alscriptstudiochecking_object::test<7>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: a NOLINT comment drops a problem and preprocessor words are explained", "[alscriptstudiochecking]") { - set_test_name("what a comment says is not wanted dropped, a comment offered for the rest; the preprocessor's words and a require explained"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a", "integer a; // NOLINT\ninteger b;\nswitch (a) { }\n"); ALScriptProblem quiet = problem(0, "a unused", ALScriptProblem::Severity::Warning); @@ -697,11 +819,17 @@ namespace tut theirs.file = "disk:/lib.lsl"; checking.ask(doc, Kind::Check, ALTextPos(), ALTextPos()); studio.asks[0].answered(answer(doc, { quiet, said, parse, theirs })); - ensure_equals("the one a comment quiets dropped", doc.check->analysis.size(), size_t(3)); - ensure_equals("an include's not explained here", doc.check->analysis[2].message, std::string("syntax error")); - ensure("the other offered a comment", doc.check->analysis[0].fixes.size() == 1 && - doc.check->analysis[0].fixes[0].kind == ALScriptFix::Kind::Suppress); - ensure("the switch explained", doc.check->analysis[1].message.find("syntax error PreprocHintSwitch [WORD]=switch") == 0); + UNSCOPED_INFO("the one a comment quiets dropped"); + REQUIRE(doc.check->analysis.size() == size_t(3)); + UNSCOPED_INFO("an include's not explained here"); + CHECK(doc.check->analysis[2].message == std::string("syntax error")); + { + INFO("the other offered a comment"); + REQUIRE(doc.check->analysis[0].fixes.size() == 1); + CHECK(doc.check->analysis[0].fixes[0].kind == ALScriptFix::Kind::Suppress); + } + UNSCOPED_INFO("the switch explained"); + CHECK(doc.check->analysis[1].message.find("syntax error PreprocHintSwitch [WORD]=switch") == 0); preprocessing = true; switches = true; doc.expanded.valid = false; @@ -712,20 +840,25 @@ namespace tut plain.map.finish(); expansions.back().second(plain); studio.asks.back().answered(answer(doc, { parse })); - ensure_equals("its transform on: not", doc.check->analysis[0].message, std::string("syntax error")); + UNSCOPED_INFO("its transform on: not"); + CHECK(doc.check->analysis[0].message == std::string("syntax error")); preprocessing = false; Doc& lua = tab("b", "local x = require(\"lib\")\n"); lua.language.lua = true; checking.ask(lua, Kind::Check, ALTextPos(), ALTextPos()); studio.asks.back().answered(answer(lua)); - ensure("a require the preprocessor does not run over, warned of", - lua.check->analysis.size() == 1 && lua.check->analysis[0].message == "RequireNotPreprocessed [NAME]=lib"); + { + INFO("a require the preprocessor does not run over, warned of"); + REQUIRE(lua.check->analysis.size() == 1); + CHECK(lua.check->analysis[0].message == "RequireNotPreprocessed [NAME]=lib"); + } preprocessing = true; checking.ask(lua, Kind::Check, ALTextPos(), ALTextPos()); expansions.back().second(plain); studio.asks.back().answered(answer(lua)); - ensure("preprocessed: not", lua.check->analysis.empty()); + UNSCOPED_INFO("preprocessed: not"); + REQUIRE(lua.check->analysis.empty()); // `inline` is taken off whenever the preprocessor runs, the // extensions on or not: said as the preprocessor's alone. @@ -733,7 +866,8 @@ namespace tut Doc& marked = tab("m", "integer f() inline { return 1; }\n"); checking.ask(marked, Kind::Check, ALTextPos(), ALTextPos()); studio.asks.back().answered(answer(marked, { problem(0, "syntax error") })); - ensure("inline explained: " + marked.check->analysis[0].message, marked.check->analysis[0].message.find("syntax error PreprocHintInline [WORD]=inline") == 0); + UNSCOPED_INFO("inline explained: " << marked.check->analysis[0].message); + CHECK(marked.check->analysis[0].message.find("syntax error PreprocHintInline [WORD]=inline") == 0); preprocessing = true; marked.expanded.valid = false; checking.ask(marked, Kind::Check, ALTextPos(), ALTextPos()); @@ -743,25 +877,31 @@ namespace tut stripped.map.finish(); expansions.back().second(stripped); studio.asks.back().answered(answer(marked, { problem(0, "syntax error") })); - ensure_equals("preprocessing, the extensions off: not", marked.check->analysis[0].message, std::string("syntax error")); + UNSCOPED_INFO("preprocessing, the extensions off: not"); + CHECK(marked.check->analysis[0].message == std::string("syntax error")); // And so is `const`, before a type anywhere on the line. preprocessing = false; Doc& constant = tab("c", "f(const integer n) { }\n"); checking.ask(constant, Kind::Check, ALTextPos(), ALTextPos()); studio.asks.back().answered(answer(constant, { problem(0, "syntax error") })); - ensure("const explained: " + constant.check->analysis[0].message, - constant.check->analysis[0].message.find("syntax error PreprocHintConst [WORD]=const") == 0); + UNSCOPED_INFO("const explained: " << constant.check->analysis[0].message); + CHECK(constant.check->analysis[0].message.find("syntax error PreprocHintConst [WORD]=const") == 0); // A script with a directive is preprocessed, the setting off or // on: the grid could compile it no other way. Doc& directive = tab("d", " #define MAX 15\ndefault { state_entry() { } }\n"); const size_t expanded = expansions.size(); - ensure("preprocessed for its directive", checking.preprocessed(directive) && - checking.preprocessedWhy(directive) == ALPreprocessor::Wanted::Directives); + { + INFO("preprocessed for its directive"); + CHECK(checking.preprocessed(directive)); + CHECK(checking.preprocessedWhy(directive) == ALPreprocessor::Wanted::Directives); + } checking.ask(directive, Kind::Check, ALTextPos(), ALTextPos()); - ensure("expanded, not read as code", expansions.size() == expanded + 1); - ensure("an envelope first", [&] { + UNSCOPED_INFO("expanded, not read as code"); + REQUIRE(expansions.size() == expanded + 1); + UNSCOPED_INFO("an envelope first"); + CHECK([&] { directive.envelope = ALScriptEnvelope(); const bool first = checking.preprocessedWhy(directive) == ALPreprocessor::Wanted::Enveloped; directive.envelope.reset(); @@ -772,54 +912,81 @@ namespace tut // preprocessor's to explain, and nothing put in where the parser // stopped. Doc& stray = tab("e", "# 15\ndefault { state_entry() { } }\n"); - ensure("not preprocessed", !checking.preprocessed(stray)); + UNSCOPED_INFO("not preprocessed"); + CHECK_FALSE(checking.preprocessed(stray)); checking.ask(stray, Kind::Check, ALTextPos(), ALTextPos()); ALScriptProblem missing = problem(0, "Missing '('."); missing.fixes.push_back(fix("Insert '('", 0, 2, 2, "(")); studio.asks.back().answered(answer(stray, { missing, problem(1, "syntax error") })); - ensure("the line explained: " + (stray.check->analysis.empty() ? std::string() : stray.check->analysis[0].message), - stray.check->analysis.size() == 2 && stray.check->analysis[0].message == "Missing '('. PreprocHintDirective" && - stray.check->analysis[0].fixes.empty()); - ensure_equals("a line of code: as the parser said", stray.check->analysis[1].message, std::string("syntax error")); + { + INFO("the line explained: " << (stray.check->analysis.empty() ? std::string() : stray.check->analysis[0].message)); + REQUIRE(stray.check->analysis.size() == 2); + CHECK(stray.check->analysis[0].message == "Missing '('. PreprocHintDirective"); + REQUIRE(stray.check->analysis[0].fixes.empty()); + } + UNSCOPED_INFO("a line of code: as the parser said"); + CHECK(stray.check->analysis[1].message == std::string("syntax error")); preprocessing = true; } - template<> template<> - void alscriptstudiochecking_object::test<8>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: a fix is made as one undo step or refused where the text moved on", "[alscriptstudiochecking]") { - set_test_name("a fix made as one step and checked again; refused where the text has moved on or its places are past the end"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); const U32 v = version(doc); - ensure("made", checking.applyFix(doc, fix("Rename it", 0, 8, 13, "total"), v)); - ensure_equals("in the text", doc.editor->document().line(0), std::string("integer total;")); - ensure_equals("one step", doc.editor->undoJournal().undoLabel(), std::string("fix")); - ensure("said, checked again", services.statuses.back() == "Rename it" && doc.check->analysisDue == 1.0); + UNSCOPED_INFO("made"); + CHECK(checking.applyFix(doc, fix("Rename it", 0, 8, 13, "total"), v)); + UNSCOPED_INFO("in the text"); + CHECK(doc.editor->document().line(0) == std::string("integer total;")); + UNSCOPED_INFO("one step"); + CHECK(doc.editor->undoJournal().undoLabel() == std::string("fix")); + { + INFO("said, checked again"); + CHECK(services.statuses.back() == "Rename it"); + CHECK(doc.check->analysisDue == 1.0); + } doc.check->analysisDue = 0.0; - ensure("moved on: refused", !checking.applyFix(doc, fix("Again", 0, 8, 13, "sum"), v)); - ensure("said, checked again", services.statuses.back() == "FixStale" && services.statusFailures.back() && - doc.check->analysisDue == 1.0); - ensure("past the end: refused", !checking.applyFix(doc, fix("Far", 40, 0, 1, "x"), version(doc))); + UNSCOPED_INFO("moved on: refused"); + CHECK_FALSE(checking.applyFix(doc, fix("Again", 0, 8, 13, "sum"), v)); + { + INFO("said, checked again"); + CHECK(services.statuses.back() == "FixStale"); + CHECK(services.statusFailures.back()); + CHECK(doc.check->analysisDue == 1.0); + } + UNSCOPED_INFO("past the end: refused"); + CHECK_FALSE(checking.applyFix(doc, fix("Far", 40, 0, 1, "x"), version(doc))); ALScriptFix refactor = fix("Extract", 0, 0, 7, "float"); refactor.kind = ALScriptFix::Kind::Refactor; - ensure("a refactor", checking.applyFix(doc, refactor, version(doc)) && doc.editor->undoJournal().undoLabel() == "refactor"); + { + INFO("a refactor"); + CHECK(checking.applyFix(doc, refactor, version(doc))); + CHECK(doc.editor->undoJournal().undoLabel() == "refactor"); + } doc.modifiable = false; - ensure("not to be changed: refused", !checking.applyFix(doc, fix("No", 0, 0, 1, "x"), version(doc))); + UNSCOPED_INFO("not to be changed: refused"); + CHECK_FALSE(checking.applyFix(doc, fix("No", 0, 0, 1, "x"), version(doc))); } - template<> template<> - void alscriptstudiochecking_object::test<9>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: Fix All waits for a check and asks before making many fixes in one step", "[alscriptstudiochecking]") { - set_test_name("Fix All: after the check where the text is not checked yet, one made at once, many asked first and made as one step"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); checking.askFixAll(doc, Doc::FixPick{}); - ensure("not checked: checked first", doc.check->fixAllAfterCheck.has_value() && services.statuses.back() == "FixChecking [NAME]=a"); + { + INFO("not checked: checked first"); + REQUIRE(doc.check->fixAllAfterCheck.has_value()); + CHECK(services.statuses.back() == "FixChecking [NAME]=a"); + } checking.pump(1.0); ALScriptProblem one = problem(0, "one"); one.fixes = { fix("First", 0, 0, 7, "float") }; studio.asks[0].answered(answer(doc, { one })); - ensure("then made", doc.editor->document().line(0) == "float count;" && !doc.check->fixAllAfterCheck); + { + INFO("then made"); + CHECK(doc.editor->document().line(0) == "float count;"); + REQUIRE_FALSE(doc.check->fixAllAfterCheck); + } checking.pump(1.0); ALScriptProblem two = problem(3, "two"); @@ -829,53 +996,78 @@ namespace tut one.fixes = { fix("First", 0, 0, 5, "integer") }; studio.asks.back().answered(answer(doc, { one, two, risky })); checking.askFixAll(doc, Doc::FixPick{}); - ensure("asked", (bool)studio.confirm && studio.confirmed["FIXES"].asString() == "Fixes [COUNT]=2"); - ensure("with what is left", studio.confirmed["LEFT"].asString() == " FixesLeft [COUNT]=1"); + { + INFO("asked"); + REQUIRE((bool)studio.confirm); + CHECK(studio.confirmed["FIXES"].asString() == "Fixes [COUNT]=2"); + } + UNSCOPED_INFO("with what is left"); + CHECK(studio.confirmed["LEFT"].asString() == " FixesLeft [COUNT]=1"); studio.confirm(); - ensure("made", doc.editor->document().line(0) == "integer count;" && - doc.editor->document().line(3) == " touch_start() { count = 1; }"); - ensure("as one step", doc.editor->undoJournal().undoLabel() == "fix" && services.statuses.back() == "FixesMade [COUNT]=2"); + { + INFO("made"); + CHECK(doc.editor->document().line(0) == "integer count;"); + CHECK(doc.editor->document().line(3) == " touch_start() { count = 1; }"); + } + { + INFO("as one step"); + CHECK(doc.editor->undoJournal().undoLabel() == "fix"); + CHECK(services.statuses.back() == "FixesMade [COUNT]=2"); + } checking.pump(1.0); studio.asks.back().answered(answer(doc)); checking.askFixAll(doc, Doc::FixPick{}); - ensure_equals("none", services.statuses.back(), std::string("FixNone")); + CHECK(services.statuses.back() == std::string("FixNone")); unit.reset(); studio.confirm(); - ensure("gone: nothing", doc.editor->document().line(0) == "integer count;"); + UNSCOPED_INFO("gone: nothing"); + CHECK(doc.editor->document().line(0) == "integer count;"); } - template<> template<> - void alscriptstudiochecking_object::test<10>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: fixesOn lists a line's fixes only for the text they were made in", "[alscriptstudiochecking]") { - set_test_name("a line's fixes as the editor lists them, of the text they were made in; and the problem a value is"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); ALScriptProblem one = problem(0, "one"); one.fixes = { fix("First", 0, 0, 7, "float") }; checking.ask(doc, Kind::Check, ALTextPos(), ALTextPos()); studio.asks[0].answered(answer(doc, { one, problem(2, "two") })); - ensure("asked for, made with the next frame", studio.waiting.count("a") == 1 && doc.shown().empty()); + { + INFO("asked for, made with the next frame"); + REQUIRE(studio.waiting.count("a") == 1); + REQUIRE(doc.shown().empty()); + } std::vector fixes; checking.fixesOn(doc, 0, fixes); - ensure("made first: one", studio.waiting.empty() && fixes.size() == 1 && fixes[0].title == "First" && - fixes[0].edits[0].second == "float"); + { + INFO("made first: one"); + REQUIRE(studio.waiting.empty()); + REQUIRE(fixes.size() == 1); + CHECK(fixes[0].title == "First"); + CHECK(fixes[0].edits[0].second == "float"); + } const Doc::Shown* shown = checking.shownOf(fixes[0].value); - ensure("its problem", shown && shown->message == "one"); + { + INFO("its problem"); + REQUIRE(shown); + CHECK(shown->message == "one"); + } fixes.clear(); checking.fixesOn(doc, 2, fixes); - ensure("none on another line", fixes.empty()); + UNSCOPED_INFO("none on another line"); + REQUIRE(fixes.empty()); doc.editor->insertText("x"); checking.fixesOn(doc, 0, fixes); - ensure("typed in since: none", fixes.empty()); + UNSCOPED_INFO("typed in since: none"); + REQUIRE(fixes.empty()); LLSD gone; gone["doc"] = "b"; - ensure("another tab's: none", !checking.shownOf(gone)); + UNSCOPED_INFO("another tab's: none"); + REQUIRE_FALSE(checking.shownOf(gone)); } - template<> template<> - void alscriptstudiochecking_object::test<11>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: changed preprocessor settings re-expand every tab a moment later", "[alscriptstudiochecking]") { - set_test_name("the preprocessor's settings changed: a moment on, every tab expanded afresh, run for a save, taught its words, checked"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); Doc& lua = tab("b"); @@ -886,25 +1078,39 @@ namespace tut checking.settingsChanged(false, 10.0); checking.settingsChanged(true, 10.1); checking.pump(10.2); - ensure("not yet", doc.expanded.valid); + UNSCOPED_INFO("not yet"); + CHECK(doc.expanded.valid); checking.pump(10.1 + 0.35); - ensure("afresh", !doc.expanded.valid && !doc.weighing->sent); - ensure_equals("run for a save", joined(studio.told), std::string("run a, run b")); - ensure("checked", doc.check->analysisDue == 0.0 && expansions.size() == 2); + { + INFO("afresh"); + CHECK_FALSE(doc.expanded.valid); + CHECK_FALSE(doc.weighing->sent); + } + UNSCOPED_INFO("run for a save"); + CHECK(joined(studio.told) == std::string("run a, run b")); + { + INFO("checked"); + CHECK(doc.check->analysisDue == 0.0); + REQUIRE(expansions.size() == 2); + } studio.told.clear(); checking.pump(20.0); - ensure("once", studio.told.empty()); + UNSCOPED_INFO("once"); + REQUIRE(studio.told.empty()); } - template<> template<> - void alscriptstudiochecking_object::test<12>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: refactors reaching past the text are dropped and other answers handed on", "[alscriptstudiochecking]") { - set_test_name("refactors offered at the caret, those reaching past the text dropped; other answers handed on or given the editor"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); checking.ask(doc, Kind::Actions, ALTextPos(0, 8), ALTextPos(0, 13)); - ensure("a stretch", studio.asks[0].request.endColumn == 13); - ensure("kept, to offer them over", doc.check->actionsAsked.begin == ALTextPos(0, 8) && doc.check->actionsAsked.end == ALTextPos(0, 13)); + UNSCOPED_INFO("a stretch"); + CHECK(studio.asks[0].request.endColumn == 13); + { + INFO("kept, to offer them over"); + CHECK(doc.check->actionsAsked.begin == ALTextPos(0, 8)); + CHECK(doc.check->actionsAsked.end == ALTextPos(0, 13)); + } ALScriptAnalysis::Result result; result.kind = Kind::Actions; result.id = "a"; @@ -916,12 +1122,17 @@ namespace tut straddle.edits[0].endLine = 9; result.actions = { inside, beyond, straddle }; studio.asks[0].answered(result); - ensure("kept", - doc.check->actions.size() == 1 && doc.check->actions[0].title == "Rename" && doc.check->actionsVersion == version(doc)); + { + INFO("kept"); + REQUIRE(doc.check->actions.size() == 1); + CHECK(doc.check->actions[0].title == "Rename"); + CHECK(doc.check->actionsVersion == version(doc)); + } result.version = version(doc) + 1; result.actions = { beyond }; studio.asks[0].answered(result); - ensure("of another text: dropped", doc.check->actions.size() == 1); + UNSCOPED_INFO("of another text: dropped"); + REQUIRE(doc.check->actions.size() == 1); checking.ask(doc, Kind::References, ALTextPos(0, 8), ALTextPos(0, 8)); ALScriptAnalysis::Result refs; refs.kind = Kind::References; @@ -930,20 +1141,24 @@ namespace tut refs.line = 0; refs.column = 8; studio.asks.back().answered(refs); - ensure_equals("handed on", studio.told.back(), std::string("references a 0:8")); + UNSCOPED_INFO("handed on"); + CHECK(studio.told.back() == std::string("references a 0:8")); doc.language.compileTarget = "none"; const size_t asks = studio.asks.size(); checking.ask(doc, Kind::Weigh, ALTextPos(), ALTextPos()); - ensure("nothing to weigh for: not asked", studio.asks.size() == asks); + UNSCOPED_INFO("nothing to weigh for: not asked"); + REQUIRE(studio.asks.size() == asks); doc.language.compileTarget = "mono"; checking.ask(doc, Kind::Weigh, ALTextPos(), ALTextPos()); - ensure("weighed for its targets", studio.asks.size() == asks + 1 && studio.asks.back().request.targets.size() == 1); + { + INFO("weighed for its targets"); + REQUIRE(studio.asks.size() == asks + 1); + REQUIRE(studio.asks.back().request.targets.size() == 1); + } } - template<> template<> - void alscriptstudiochecking_object::test<13>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: one expansion at a time while typing and then one for the latest text", "[alscriptstudiochecking]") { - set_test_name("preprocessed, typed in while an expansion is out: one at a time, then one for the latest text, not one a key"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); preprocessing = true; @@ -952,85 +1167,117 @@ namespace tut checking.ask(doc, Kind::Signature, ALTextPos(0, 2), ALTextPos(0, 2)); doc.editor->insertText(" "); checking.ask(doc, Kind::Signature, ALTextPos(0, 3), ALTextPos(0, 3)); - ensure_equals("one on its way, not one a key", expansions.size(), size_t(1)); + UNSCOPED_INFO("one on its way, not one a key"); + REQUIRE(expansions.size() == size_t(1)); expansions[0].second(expansion(doc.editor->text())); - ensure_equals("the next, for the text as it is now", expansions.size(), size_t(2)); - ensure("the latest question still waiting", doc.check->waiting.size() == 1 && doc.check->waiting[0].at == ALTextPos(0, 3) && studio.asks.empty()); + UNSCOPED_INFO("the next, for the text as it is now"); + REQUIRE(expansions.size() == size_t(2)); + { + INFO("the latest question still waiting"); + REQUIRE(doc.check->waiting.size() == 1); + CHECK(doc.check->waiting[0].at == ALTextPos(0, 3)); + REQUIRE(studio.asks.empty()); + } expansions[1].second(expansion(doc.editor->text())); - ensure_equals("then asked, once", studio.asks.size(), size_t(1)); - ensure("of the latest", studio.asks[0].request.version == version(doc)); + UNSCOPED_INFO("then asked, once"); + REQUIRE(studio.asks.size() == size_t(1)); + UNSCOPED_INFO("of the latest"); + CHECK(studio.asks[0].request.version == version(doc)); } - template<> template<> - void alscriptstudiochecking_object::test<14>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: the front tab is weighed with its check and a tab behind only checked", "[alscriptstudiochecking]") { - set_test_name("the tab in front weighed with its check, in the one job, and its weights handed on; a tab behind checked alone"); ALScriptStudioChecking& checking = make(); Doc& a = tab("a"); Doc& b = tab("b"); studio.front = "a"; checking.ask(a, Kind::Check, ALTextPos(), ALTextPos()); checking.ask(b, Kind::Check, ALTextPos(), ALTextPos()); - ensure_equals("both asked", studio.asks.size(), size_t(2)); - ensure("the front one with its targets", studio.asks[0].request.front && studio.asks[0].request.targets.size() == 1); - ensure("and noted as asked for", a.weighing->askedFor == version(a)); - ensure("the other without", !studio.asks[1].request.front && studio.asks[1].request.targets.empty() && b.weighing->askedFor != version(b)); + UNSCOPED_INFO("both asked"); + REQUIRE(studio.asks.size() == size_t(2)); + { + INFO("the front one with its targets"); + CHECK(studio.asks[0].request.front); + REQUIRE(studio.asks[0].request.targets.size() == 1); + } + UNSCOPED_INFO("and noted as asked for"); + CHECK(a.weighing->askedFor == version(a)); + { + INFO("the other without"); + CHECK_FALSE(studio.asks[1].request.front); + REQUIRE(studio.asks[1].request.targets.empty()); + CHECK(b.weighing->askedFor != version(b)); + } ALScriptAnalysis::Result weighed = answer(a, {}); ALScriptWeight weight; weight.target = ALScriptWeight::Target::Mono; weight.total = 100; weighed.weights = { weight }; studio.asks[0].answered(weighed); - ensure_equals("its problems shown", joined(studio.told), std::string("problems a, outline a")); - ensure("its weights handed on with the check", a.weighing->weight && a.weighing->weight->total == 100); + UNSCOPED_INFO("its problems shown"); + CHECK(joined(studio.told) == std::string("problems a, outline a")); + { + INFO("its weights handed on with the check"); + REQUIRE(a.weighing->weight); + CHECK(a.weighing->weight->total == 100); + } studio.told.clear(); studio.asks[1].answered(answer(b, {})); - ensure_equals("none for the other", joined(studio.told), std::string("problems b, outline b")); + UNSCOPED_INFO("none for the other"); + CHECK(joined(studio.told) == std::string("problems b, outline b")); } - template<> template<> - void alscriptstudiochecking_object::test<15>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: relint refilters LSL's last check and rechecks SLua or edited tabs", "[alscriptstudiochecking]") { - set_test_name("the lints chosen again: an LSL tab's last check filtered afresh, nothing asked; an SLua tab, or a text since typed in, checked again"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); checking.schedule(doc, true); checking.pump(1.0); studio.asks[0].answered(answer(doc, { problem(0, "wrong"), problem(1, "unused", ALScriptProblem::Severity::Warning) })); - ensure_equals("both taken", doc.check->analysis.size(), size_t(2)); + UNSCOPED_INFO("both taken"); + REQUIRE(doc.check->analysis.size() == size_t(2)); studio.told.clear(); gWarningsOff = true; checking.relint(doc); - ensure_equals("nothing asked", studio.asks.size(), size_t(1)); - ensure("the warning filtered out", doc.check->analysis.size() == 1 && doc.check->analysis[0].message == "wrong"); - ensure_equals("and shown", joined(studio.told), std::string("problems a")); + UNSCOPED_INFO("nothing asked"); + REQUIRE(studio.asks.size() == size_t(1)); + { + INFO("the warning filtered out"); + REQUIRE(doc.check->analysis.size() == 1); + CHECK(doc.check->analysis[0].message == "wrong"); + } + UNSCOPED_INFO("and shown"); + CHECK(joined(studio.told) == std::string("problems a")); gWarningsOff = false; checking.relint(doc); - ensure_equals("back again, from what the check said", doc.check->analysis.size(), size_t(2)); + UNSCOPED_INFO("back again, from what the check said"); + REQUIRE(doc.check->analysis.size() == size_t(2)); doc.editor->insertText("x"); checking.relint(doc); checking.pump(2.0); - ensure_equals("typed in since: checked again", studio.asks.size(), size_t(2)); + UNSCOPED_INFO("typed in since: checked again"); + REQUIRE(studio.asks.size() == size_t(2)); Doc& lua = tab("b"); lua.language.lua = true; const size_t asks = studio.asks.size(); checking.relint(lua); checking.pump(3.0); - ensure_equals("SLua: checked again", studio.asks.size(), asks + 1); + UNSCOPED_INFO("SLua: checked again"); + REQUIRE(studio.asks.size() == asks + 1); } - template<> template<> - void alscriptstudiochecking_object::test<16>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: SLua with requires is checked apart with its modules and its bundle", "[alscriptstudiochecking]") { - set_test_name("SLua with requires: the analyzers asked of the script apart, its modules and which require reaches which, the bundle to weigh; a module's problem in its file"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); doc.language.lua = true; doc.editor->setText("local util = require(\"util\")\nprint(util.x)\n"); preprocessing = true; checking.ask(doc, Kind::Check, ALTextPos(), ALTextPos()); - ensure_equals("expanded", expansions.size(), size_t(1)); - ensure("apart asked for", expansions[0].first.apart); + UNSCOPED_INFO("expanded"); + REQUIRE(expansions.size() == size_t(1)); + UNSCOPED_INFO("apart asked for"); + CHECK(expansions[0].first.apart); ALPreprocessor::Result result; result.map.addFile("a", "object:a"); result.map.addFile("util", "object:util"); @@ -1067,13 +1314,28 @@ namespace tut result.apart.modules.push_back(util); result.resolved.push_back({ "", "util", true, "object:util" }); expansions[0].second(result); - ensure_equals("asked", studio.asks.size(), size_t(1)); + UNSCOPED_INFO("asked"); + REQUIRE(studio.asks.size() == size_t(1)); const ALScriptAnalysis::Request& asked = studio.asks[0].request; - ensure("of the script apart", *asked.text == doc.editor->text()); - ensure("with its module", asked.modules && asked.modules->modules.size() == 1 && asked.modules->modules[0].key == "object:util"); - ensure("and which require reaches it", - asked.modules->reaches.size() == 1 && asked.modules->reaches[0].name == "util" && asked.modules->reaches[0].key == "object:util"); - ensure("the bundle to weigh", asked.bundle && *asked.bundle == result.text); + UNSCOPED_INFO("of the script apart"); + CHECK(*asked.text == doc.editor->text()); + { + INFO("with its module"); + REQUIRE(asked.modules); + REQUIRE(asked.modules->modules.size() == 1); + CHECK(asked.modules->modules[0].key == "object:util"); + } + { + INFO("and which require reaches it"); + REQUIRE(asked.modules->reaches.size() == 1); + CHECK(asked.modules->reaches[0].name == "util"); + CHECK(asked.modules->reaches[0].key == "object:util"); + } + { + INFO("the bundle to weigh"); + REQUIRE(asked.bundle); + CHECK(*asked.bundle == result.text); + } // What the checker said of the module, in the module's lines: in // its file, where its map puts it. ALScriptProblem in_module = problem(1, "wrong in util"); @@ -1085,14 +1347,14 @@ namespace tut mapped |= p.message == "wrong in util" && p.file == "object:util" && p.line == 11 && p.fixes.empty(); own |= p.message == "wrong here" && p.file.empty() && p.line == 1; } - ensure("the module's, in its file's lines", mapped); - ensure("the script's own, in its", own); + UNSCOPED_INFO("the module's, in its file's lines"); + CHECK(mapped); + UNSCOPED_INFO("the script's own, in its"); + CHECK(own); } - template<> template<> - void alscriptstudiochecking_object::test<17>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: a name declared const is said to be const where it is inspected", "[alscriptstudiochecking]") { - set_test_name("a name declared const, which the analyzers read with the word taken off, is said to be const where it is inspected"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); preprocessing = true; @@ -1115,14 +1377,14 @@ namespace tut studio.asks.back().answered(result); return studio.labels.empty() ? std::string() : studio.labels.back(); }; - ensure_equals("said const", inspect(1, 8, "integer count"), std::string("const integer count")); - ensure_equals("another name is not", inspect(0, 8, "integer helper"), std::string("integer helper")); + UNSCOPED_INFO("said const"); + CHECK(inspect(1, 8, "integer count") == std::string("const integer count")); + UNSCOPED_INFO("another name is not"); + CHECK(inspect(0, 8, "integer helper") == std::string("integer helper")); } - template<> template<> - void alscriptstudiochecking_object::test<18>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: the front tab is checked when due and others one a frame oldest first", "[alscriptstudiochecking]") { - set_test_name("the tab in front checked as soon as it is due; the others one a frame, the one due longest first"); ALScriptStudioChecking& checking = make(); Doc& a = tab("a"); Doc& b = tab("b"); @@ -1138,21 +1400,26 @@ namespace tut { asked += one.request.id; } - ensure_equals("in front, and the one due longest", asked, std::string("ca")); + UNSCOPED_INFO("in front, and the one due longest"); + CHECK(asked == std::string("ca")); checking.pump(1.0); - ensure("then the next", studio.asks.size() == 3 && studio.asks[2].request.id == "b"); + { + INFO("then the next"); + REQUIRE(studio.asks.size() == 3); + CHECK(studio.asks[2].request.id == "b"); + } checking.pump(1.0); - ensure_equals("and none again", studio.asks.size(), size_t(3)); + UNSCOPED_INFO("and none again"); + REQUIRE(studio.asks.size() == size_t(3)); } - template<> template<> - void alscriptstudiochecking_object::test<19>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: the inspector reuses the tip's answer for the same word and text", "[alscriptstudiochecking]") { - set_test_name("the tip and the inspector ask the same question: the second of a word, as the text stands, told what the first was"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); checking.ask(doc, Kind::Hover, ALTextPos(0, 8), ALTextPos(0, 8)); - ensure_equals("the tip asks", studio.asks.size(), size_t(1)); + UNSCOPED_INFO("the tip asks"); + REQUIRE(studio.asks.size() == size_t(1)); ALScriptAnalysis::Result said; said.kind = Kind::Hover; said.id = "a"; @@ -1163,21 +1430,24 @@ namespace tut said.hover.label = "integer count"; studio.asks[0].answered(said); checking.ask(doc, Kind::Inspect, ALTextPos(0, 10), ALTextPos(0, 10)); - ensure_equals("the inspector, of the same word: not asked", studio.asks.size(), size_t(1)); - ensure_equals("but told", studio.told.back(), std::string("inspect a 0:10")); - ensure_equals("what the tip was", studio.labels.back(), std::string("integer count")); + UNSCOPED_INFO("the inspector, of the same word: not asked"); + REQUIRE(studio.asks.size() == size_t(1)); + UNSCOPED_INFO("but told"); + CHECK(studio.told.back() == std::string("inspect a 0:10")); + UNSCOPED_INFO("what the tip was"); + CHECK(studio.labels.back() == std::string("integer count")); checking.ask(doc, Kind::Inspect, ALTextPos(1, 0), ALTextPos(1, 0)); - ensure_equals("another word: asked", studio.asks.size(), size_t(2)); + UNSCOPED_INFO("another word: asked"); + REQUIRE(studio.asks.size() == size_t(2)); doc.editor->setCaret(ALTextPos(0, 0)); doc.editor->insertText(" "); checking.ask(doc, Kind::Inspect, ALTextPos(0, 10), ALTextPos(0, 10)); - ensure_equals("typed in since: asked", studio.asks.size(), size_t(3)); + UNSCOPED_INFO("typed in since: asked"); + REQUIRE(studio.asks.size() == size_t(3)); } - template<> template<> - void alscriptstudiochecking_object::test<20>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: a notecard is checked as scripts read it and outlined only as JSON", "[alscriptstudiochecking]") { - set_test_name("a notecard checked as scripts read it, with no analyzer asked: its items making every line EOF, a line past 1024 bytes; outlined as JSON only"); ALScriptStudioChecking& checking = make(); const std::string long_line(1030, 'x'); Doc& card = tab("card", "one " + ALNotecardItems::charOf(0) + "\n" + long_line + "\n"); @@ -1189,33 +1459,48 @@ namespace tut card.items->loaded({ LLPointer(new LLInventoryItem()) }); checking.schedule(card, true); checking.pump(2.0); - ensure("no analyzer asked", studio.asks.empty()); - ensure_equals("two", card.check->analysis.size(), size_t(2)); - ensure("the items, at the first", card.check->analysis[0].line == 0 && card.check->analysis[0].column == 4 && - card.check->analysis[0].severity == ALScriptProblem::Severity::Warning); - ensure("the long line, from where it is cut", card.check->analysis[1].line == 1 && card.check->analysis[1].column == 1024 && - card.check->analysis[1].endColumn == 1030); - ensure("no outline as plain text", card.outline.empty()); + UNSCOPED_INFO("no analyzer asked"); + REQUIRE(studio.asks.empty()); + REQUIRE(card.check->analysis.size() == size_t(2)); + { + INFO("the items, at the first"); + CHECK(card.check->analysis[0].line == 0); + CHECK(card.check->analysis[0].column == 4); + CHECK(card.check->analysis[0].severity == ALScriptProblem::Severity::Warning); + } + { + INFO("the long line, from where it is cut"); + CHECK(card.check->analysis[1].line == 1); + CHECK(card.check->analysis[1].column == 1024); + CHECK(card.check->analysis[1].endColumn == 1030); + } + UNSCOPED_INFO("no outline as plain text"); + REQUIRE(card.outline.empty()); card.items.reset(); card.grammar = "json"; card.editor->setText("{ \"door\": { \"speed\": 2 } }\n"); checking.schedule(card, true); checking.pump(3.0); - ensure("nothing to say", card.check->analysis.empty()); - ensure("its keys outlined", card.outline.size() == 2 && card.outline[0].name == "door" && card.outline[1].name == "speed"); + UNSCOPED_INFO("nothing to say"); + REQUIRE(card.check->analysis.empty()); + { + INFO("its keys outlined"); + REQUIRE(card.outline.size() == 2); + CHECK(card.outline[0].name == "door"); + CHECK(card.outline[1].name == "speed"); + } Doc& file = tab("disk", "{ \"a\": 1 }\n"); file.file = "/tmp/a.json"; file.notecard = true; checking.schedule(file, true); - ensure("a text file on disk is not checked", file.check->analysisDue <= 0.0); + UNSCOPED_INFO("a text file on disk is not checked"); + CHECK(file.check->analysisDue <= 0.0); } - template<> template<> - void alscriptstudiochecking_object::test<21>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: Fix All previewed beside the text applies as one step unless it changed", "[alscriptstudiochecking]") { - set_test_name("Fix All previewed: the text after beside the text now, Apply offered, made as one step; not over a text changed since"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); // Asked before the check, asked again once it has answered. @@ -1227,17 +1512,31 @@ namespace tut two.fixes = { fix("Second", 3, 4, 15, "touch_start") }; const std::string before = doc.editor->wholeText(); studio.asks.back().answered(answer(doc, { one, two })); - ensure("asked, with a preview", (bool)studio.preview); + UNSCOPED_INFO("asked, with a preview"); + REQUIRE((bool)studio.preview); studio.preview(); - ensure("nothing made yet", doc.editor->wholeText() == before); + UNSCOPED_INFO("nothing made yet"); + CHECK(doc.editor->wholeText() == before); const std::string& compared = studio.told.back(); - ensure("the text now beside it after" + compared, - compared.rfind("compare a: " + before + " | float count;", 0) == 0 && compared.find(" touch_start() { count = 1; }") != std::string::npos && - compared.find("(CompareNow | FixAllAfter [FIXES]=Fixes [COUNT]=2") != std::string::npos); - ensure("Apply offered", services.reports.back().actions == Names{ "apply_fixes" } && services.reports.back().doc == "a"); - ensure("made", checking.applyPreviewed(doc) && doc.editor->document().line(0) == "float count;" && - doc.editor->undoJournal().undoLabel() == "fix"); - ensure("once", !checking.applyPreviewed(doc)); + { + INFO("the text now beside it after: " << compared); + CHECK(compared.rfind("compare a: " + before + " | float count;", 0) == 0); + REQUIRE(compared.find(" touch_start() { count = 1; }") != std::string::npos); + REQUIRE(compared.find("(CompareNow | FixAllAfter [FIXES]=Fixes [COUNT]=2") != std::string::npos); + } + { + INFO("Apply offered"); + CHECK(services.reports.back().actions == Names{ "apply_fixes" }); + CHECK(services.reports.back().doc == "a"); + } + { + INFO("made"); + CHECK(checking.applyPreviewed(doc)); + CHECK(doc.editor->document().line(0) == "float count;"); + CHECK(doc.editor->undoJournal().undoLabel() == "fix"); + } + UNSCOPED_INFO("once"); + CHECK_FALSE(checking.applyPreviewed(doc)); checking.pump(1.0); ALScriptProblem three = problem(0, "three"); @@ -1247,18 +1546,24 @@ namespace tut studio.preview = nullptr; studio.asks.back().answered(answer(doc, { three, four })); checking.askFixAll(doc, Doc::FixPick{}); - ensure("asked again", (bool)studio.preview); + UNSCOPED_INFO("asked again"); + REQUIRE((bool)studio.preview); studio.preview(); doc.editor->setText("default {}\n"); - ensure("changed since: not made", !checking.applyPreviewed(doc) && doc.editor->wholeText() == "default {}\n"); - ensure("said", services.statuses.back() == "FixAllChangedSince" && services.statusFailures.back()); + { + INFO("changed since: not made"); + CHECK_FALSE(checking.applyPreviewed(doc)); + CHECK(doc.editor->wholeText() == "default {}\n"); + } + { + INFO("said"); + CHECK(services.statuses.back() == "FixAllChangedSince"); + CHECK(services.statusFailures.back()); + } } - template<> template<> - void alscriptstudiochecking_object::test<22>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: each LSL comment in an SLua script is a note to see to and none in LSL", "[alscriptstudiochecking]") { - set_test_name("each LSL comment in an SLua script a note to see to, before the check's own: Done, and the lint's fix of the line it " - "stands over with it; read again, not twice; none in LSL"); ALScriptStudioChecking& checking = make(); const std::string text = "local s = \"abc\"\n" "-- LSL: llcompat.GetSubString takes indexes from 0, as LSL did; ll.GetSubString takes them from 1.\n" @@ -1275,31 +1580,51 @@ namespace tut compat.fixes = { fix("Write it ll.GetSubString(s, 1, 3)", 2, 6, 36, "ll.GetSubString(s, 1, 3)") }; studio.asks.back().answered(answer(doc, { compat })); const ALScriptProblems& said = doc.check->analysis; - ensure_equals("two notes, then the lint", said.size(), size_t(3)); - ensure("the notes first", said[0].key == "SluaNote" && said[0].line == 1 && said[1].key == "SluaNote" && said[1].line == 3 && - said[2].code == "SlCompatCall"); - ensure("the lint's fix with the note's", said[0].fixes.size() == 2 && said[0].fixes[0].preferred && - said[0].fixes[0].title == "Write it ll.GetSubString(s, 1, 3), and take the note out"); - ensure("the other: Done alone", said[1].fixes.size() == 1 && !said[1].fixes[0].preferred); - ensure("made", checking.applyFix(doc, said[0].fixes[0], version(doc)) && - doc.editor->wholeText() == "local s = \"abc\"\nprint(ll.GetSubString(s, 1, 3))\n-- LSL: something else\nprint(s)\n"); + UNSCOPED_INFO("two notes, then the lint"); + REQUIRE(said.size() == size_t(3)); + { + INFO("the notes first"); + CHECK(said[0].key == "SluaNote"); + CHECK(said[0].line == 1); + CHECK(said[1].key == "SluaNote"); + CHECK(said[1].line == 3); + CHECK(said[2].code == "SlCompatCall"); + } + { + INFO("the lint's fix with the note's"); + REQUIRE(said[0].fixes.size() == 2); + CHECK(said[0].fixes[0].preferred); + CHECK(said[0].fixes[0].title == "Write it ll.GetSubString(s, 1, 3), and take the note out"); + } + { + INFO("the other: Done alone"); + REQUIRE(said[1].fixes.size() == 1); + CHECK_FALSE(said[1].fixes[0].preferred); + } + { + INFO("made"); + CHECK(checking.applyFix(doc, said[0].fixes[0], version(doc))); + CHECK(doc.editor->wholeText() == "local s = \"abc\"\nprint(ll.GetSubString(s, 1, 3))\n-- LSL: something else\nprint(s)\n"); + } checking.pump(2.0); studio.asks.back().answered(answer(doc)); - ensure("read again: the one left, once", doc.check->analysis.size() == 1 && doc.check->analysis[0].line == 2); + { + INFO("read again: the one left, once"); + REQUIRE(doc.check->analysis.size() == 1); + CHECK(doc.check->analysis[0].line == 2); + } Doc& lsl = tab("b", "// -- LSL: not SLua\ndefault { state_entry() {} }\n"); checking.schedule(lsl, true); checking.pump(3.0); studio.asks.back().answered(answer(lsl)); - ensure("none in LSL", lsl.check->analysis.empty()); + UNSCOPED_INFO("none in LSL"); + REQUIRE(lsl.check->analysis.empty()); } - template<> template<> - void alscriptstudiochecking_object::test<23>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: Fix All of what LSL left previews when unsafe and asks when all safe", "[alscriptstudiochecking]") { - set_test_name("what is left from LSL fixed: every preferred fix of it, not another problem's; seen first where any could change " - "what the script does, asked as Fix All asks where none could"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); doc.language.lua = true; @@ -1320,12 +1645,21 @@ namespace tut pick.migration = true; studio.preview = nullptr; checking.askFixAll(doc, pick); - ensure("not asked: seen", !studio.preview); + UNSCOPED_INFO("not asked: seen"); + REQUIRE_FALSE(studio.preview); const std::string& compared = studio.told.back(); - ensure("the two from LSL, not the other: " + compared, compared.find("FixAllAfter [FIXES]=Fixes [COUNT]=2") != std::string::npos && - compared.find("float count;") != std::string::npos); - ensure("Apply offered", services.reports.back().actions == Names{ "apply_fixes" }); - ensure("made", checking.applyPreviewed(doc) && doc.editor->document().line(0) == "float count;"); + { + INFO("the two from LSL, not the other: " << compared); + REQUIRE(compared.find("FixAllAfter [FIXES]=Fixes [COUNT]=2") != std::string::npos); + REQUIRE(compared.find("float count;") != std::string::npos); + } + UNSCOPED_INFO("Apply offered"); + CHECK(services.reports.back().actions == Names{ "apply_fixes" }); + { + INFO("made"); + CHECK(checking.applyPreviewed(doc)); + CHECK(doc.editor->document().line(0) == "float count;"); + } Doc& safe = tab("b"); safe.language.lua = true; @@ -1336,13 +1670,12 @@ namespace tut studio.asks.back().answered(answer(safe, { first, second })); studio.preview = nullptr; checking.askFixAll(safe, pick); - ensure("all safe: asked as Fix All asks", (bool)studio.preview); + UNSCOPED_INFO("all safe: asked as Fix All asks"); + REQUIRE((bool)studio.preview); } - template<> template<> - void alscriptstudiochecking_object::test<24>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: an unsafe fix of what LSL left is previewed and made by Apply", "[alscriptstudiochecking]") { - set_test_name("what is left from LSL put right where a fix could change the script: previewed beside the text, and made by Apply"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a", "local t = {}\nif #t then print(1) end\n"); doc.language.lua = true; @@ -1356,18 +1689,27 @@ namespace tut Doc::FixPick pick; pick.migration = true; checking.askFixAll(doc, pick); - ensure("previewed, not asked", !studio.preview && studio.told.back().rfind("compare a: ", 0) == 0); - ensure("Apply offered", services.reports.back().actions == Names{ "apply_fixes" }); - ensure("made by Apply", checking.applyPreviewed(doc)); - ensure_equals("put right", doc.editor->document().line(1), std::string("if #t ~= 0 then print(1) end")); - ensure("Apply again: nothing waiting, and said", !checking.applyPreviewed(doc) && services.statuses.back() == "FixAllNotPreviewed" && - services.statusFailures.back()); + { + INFO("previewed, not asked"); + REQUIRE_FALSE(studio.preview); + CHECK(studio.told.back().rfind("compare a: ", 0) == 0); + } + UNSCOPED_INFO("Apply offered"); + CHECK(services.reports.back().actions == Names{ "apply_fixes" }); + UNSCOPED_INFO("made by Apply"); + CHECK(checking.applyPreviewed(doc)); + UNSCOPED_INFO("put right"); + CHECK(doc.editor->document().line(1) == std::string("if #t ~= 0 then print(1) end")); + { + INFO("Apply again: nothing waiting, and said"); + CHECK_FALSE(checking.applyPreviewed(doc)); + CHECK(services.statuses.back() == "FixAllNotPreviewed"); + CHECK(services.statusFailures.back()); + } } - template<> template<> - void alscriptstudiochecking_object::test<25>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: a require alias fix names its folder only once its edit takes", "[alscriptstudiochecking]") { - set_test_name("a fix that moves a require onto a SLua alias of the studio's own names the folder once its edit is sure to take, and is refused with nothing changed where the name is another folder's"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); std::vector named; @@ -1379,9 +1721,12 @@ namespace tut ALScriptFix moved = fix("Name /inc as the SLua alias @inc, and require it as '@inc/util'", 0, 8, 13, "total", false); moved.key = "ScriptFixRequireAlias"; moved.args = { "@inc/util", "inc", "/inc" }; - ensure("made", checking.applyFix(doc, moved, version(doc))); - ensure("the folder named", named == std::vector{ "inc=/inc" }); - ensure_equals("and the text", doc.editor->document().line(0), std::string("integer total;")); + UNSCOPED_INFO("made"); + CHECK(checking.applyFix(doc, moved, version(doc))); + UNSCOPED_INFO("the folder named"); + CHECK(named == std::vector{ "inc=/inc" }); + UNSCOPED_INFO("and the text"); + CHECK(doc.editor->document().line(0) == std::string("integer total;")); ALScriptFix taken = fix("Name /lib as the SLua alias @inc, and require it as '@inc/util'", 0, 8, 13, "other", false); taken.key = "ScriptFixRequireAlias"; taken.args = { "@inc/util", "inc", "/lib" }; @@ -1389,34 +1734,50 @@ namespace tut // change, or that it would take past its limit. named.clear(); doc.editor->setReadOnly(true); - ensure("not made", !checking.applyFix(doc, taken, version(doc))); - ensure("and nothing named", named.empty()); + UNSCOPED_INFO("not made"); + CHECK_FALSE(checking.applyFix(doc, taken, version(doc))); + UNSCOPED_INFO("and nothing named"); + REQUIRE(named.empty()); doc.editor->setReadOnly(false); ALScriptFix longer = taken; longer.edits[0].text = "a_name_longer_than_the_text_has_room_for"; doc.editor->setMaxBytes(doc.editor->document().byteCount()); - ensure("past the limit: not made", !checking.applyFix(doc, longer, version(doc))); - ensure("nor named", named.empty()); + UNSCOPED_INFO("past the limit: not made"); + CHECK_FALSE(checking.applyFix(doc, longer, version(doc))); + UNSCOPED_INFO("nor named"); + REQUIRE(named.empty()); doc.editor->setMaxBytes(0); free = false; const U32 was_at = version(doc); - ensure("the name another folder's: refused", !checking.applyFix(doc, taken, version(doc))); - ensure("asked", named == std::vector{ "inc=/lib" }); - ensure("said", services.statuses.back().find("AliasTaken [NAME]=inc") == 0 && services.statusFailures.back()); - ensure("nothing changed", version(doc) == was_at && doc.editor->document().line(0) == "integer total;"); - ensure("nothing to redo", !doc.editor->undoJournal().canRedo()); + UNSCOPED_INFO("the name another folder's: refused"); + CHECK_FALSE(checking.applyFix(doc, taken, version(doc))); + UNSCOPED_INFO("asked"); + CHECK(named == std::vector{ "inc=/lib" }); + { + INFO("said"); + CHECK(services.statuses.back().find("AliasTaken [NAME]=inc") == 0); + CHECK(services.statusFailures.back()); + } + { + INFO("nothing changed"); + CHECK(version(doc) == was_at); + CHECK(doc.editor->document().line(0) == "integer total;"); + } + UNSCOPED_INFO("nothing to redo"); + CHECK_FALSE(doc.editor->undoJournal().canRedo()); ALScriptFix plain = fix("Require it as './util'", 0, 8, 13, "sum", false); plain.key = "ScriptFixRequireAs"; plain.args = { "./util" }; named.clear(); - ensure("a plain one names nothing", checking.applyFix(doc, plain, version(doc)) && named.empty()); + { + INFO("a plain one names nothing"); + CHECK(checking.applyFix(doc, plain, version(doc))); + REQUIRE(named.empty()); + } } - template<> template<> - void alscriptstudiochecking_object::test<26>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: an include's file keeps its old answer until relexed to its end", "[alscriptstudiochecking]") { - set_test_name("an include's file asked of again after an edit that changes how every line after it starts: what it was, while the text " - "is lexed to its end a slice at a time, then what it is; a check sent of the text as it stands"); ALScriptStudioChecking& checking = make(); std::string text; for (S32 i = 0; i < 3000; ++i) @@ -1426,34 +1787,41 @@ namespace tut Doc& doc = tab("disk:/lib.lsl", text); doc.file = "/lib.lsl"; doc.editor->setSyntax("lsl"); - ensure("a fragment", checking.lslFragment(doc)); + CHECK(checking.lslFragment(doc)); ALSyntaxHighlighter& highlighter = doc.editor->highlighter(); const S32 last = doc.editor->document().lineCount() - 1; // A string opened at the top runs on to the end: every line after // it starts in it. doc.editor->document().insert(ALTextPos(0, 0), "\""); - ensure("asked again at once: a fragment", checking.lslFragment(doc)); + UNSCOPED_INFO("asked again at once: a fragment"); + CHECK(checking.lslFragment(doc)); highlighter.tokens(last); - ensure("the text not lexed to its end for it: " + std::to_string(highlighter.lastLexed()) + " of " + std::to_string(last + 1) + " left", - highlighter.lastLexed() > last / 2); - ensure("nor kept for the text as it stands", doc.check->fragment->first != version(doc)); - ensure("lexed to its end: what it is, kept", checking.lslFragment(doc) && doc.check->fragment->first == version(doc)); + UNSCOPED_INFO("the text not lexed to its end for it: " << highlighter.lastLexed() << " of " << last + 1 << " left"); + CHECK(highlighter.lastLexed() > last / 2); + UNSCOPED_INFO("nor kept for the text as it stands"); + CHECK(doc.check->fragment->first != version(doc)); + { + INFO("lexed to its end: what it is, kept"); + CHECK(checking.lslFragment(doc)); + CHECK(doc.check->fragment->first == version(doc)); + } // A state at the end of a text set whole: still lexed when the check // is asked, which sends it as it stands, with no state put after it. doc.editor->setText(text + "default\n{\n state_entry() { }\n}\n"); checking.lslFragment(doc); checking.ask(doc, Kind::Check, ALTextPos(), ALTextPos()); - ensure_equals("asked once", studio.asks.size(), size_t(1)); - ensure("of the script as it stands", *studio.asks[0].request.text == doc.editor->text()); - ensure("and a script now", !checking.lslFragment(doc)); + UNSCOPED_INFO("asked once"); + REQUIRE(studio.asks.size() == size_t(1)); + UNSCOPED_INFO("of the script as it stands"); + CHECK(*studio.asks[0].request.text == doc.editor->text()); + UNSCOPED_INFO("and a script now"); + CHECK_FALSE(checking.lslFragment(doc)); } - template<> template<> - void alscriptstudiochecking_object::test<27>() + TEST_CASE_METHOD(alscriptstudiochecking_data, "alscriptstudiochecking: a require alias fix is held to the byte limit leaving out a composition", "[alscriptstudiochecking]") { - set_test_name("a fix that moves a require onto a SLua alias of the studio's own is held to the text's limit as its edit is, without a composition standing in the text"); ALScriptStudioChecking& checking = make(); Doc& doc = tab("a"); std::vector named; @@ -1466,19 +1834,25 @@ namespace tut ALCodeEditor& editor = *doc.editor; editor.setCaret(editor.document().end()); editor.preeditor().updatePreedit("xyz", LLPreeditor::segment_lengths_t{ 3 }, LLPreeditor::standouts_t{ false }, 3); - ensure("composing", editor.hasPreedit()); + UNSCOPED_INFO("composing"); + CHECK(editor.hasPreedit()); editor.setMaxBytes(SCRIPT.size() + 2); ALScriptFix longer = fix("Name /inc as the SLua alias @inc, and require it as '@inc/util'", 0, 8, 13, "counter_", false); longer.key = "ScriptFixRequireAlias"; longer.args = { "@inc/util", "inc", "/inc" }; - ensure("three more: past the limit, not made", !checking.applyFix(doc, longer, version(doc))); - ensure("nor named", named.empty()); + UNSCOPED_INFO("three more: past the limit, not made"); + CHECK_FALSE(checking.applyFix(doc, longer, version(doc))); + UNSCOPED_INFO("nor named"); + REQUIRE(named.empty()); ALScriptFix moved = longer; moved.edits[0].text = "counter"; - ensure("two more: made", checking.applyFix(doc, moved, version(doc))); - ensure("the folder named", named == std::vector{ "inc=/inc" }); - ensure_equals("and the text, the composition gone", editor.text(), "integer counter" + SCRIPT.substr(13)); + UNSCOPED_INFO("two more: made"); + CHECK(checking.applyFix(doc, moved, version(doc))); + UNSCOPED_INFO("the folder named"); + CHECK(named == std::vector{ "inc=/inc" }); + UNSCOPED_INFO("and the text, the composition gone"); + CHECK(editor.text() == "integer counter" + SCRIPT.substr(13)); editor.setMaxBytes(0); } } diff --git a/indra/newview/tests/alscriptstudiocommands_test.cpp b/indra/newview/tests/alscriptstudiocommands_test.cpp index c154a1e877..6ce86db936 100644 --- a/indra/newview/tests/alscriptstudiocommands_test.cpp +++ b/indra/newview/tests/alscriptstudiocommands_test.cpp @@ -26,11 +26,11 @@ #include "../alscriptstudiocommands.h" -#include "../test/lltut.h" +#include #include -namespace tut +namespace { struct alscriptstudiocommands_data { @@ -39,82 +39,99 @@ namespace tut std::vector done; }; - typedef test_group alscriptstudiocommands_group; - typedef alscriptstudiocommands_group::object alscriptstudiocommands_object; - alscriptstudiocommands_group alscriptstudiocommands_instance("alscriptstudiocommands"); - - template<> template<> - void alscriptstudiocommands_object::test<1>() + TEST_CASE_METHOD(alscriptstudiocommands_data, "alscriptstudiocommands: a command runs by name and reports enabled and checked state", "[alscriptstudiocommands]") { - set_test_name("a command run by name, enabled where it says so or says nothing, and on where it is a toggle that is"); bool can = false; bool wrap = true; - ensure("registered", commands.add("save", [this]() { done.push_back("save"); }, [&can]() { return can; })); - ensure("and a toggle", commands.add("word_wrap", [&wrap]() { wrap = !wrap; }, nullptr, [&wrap]() { return wrap; })); - ensure("and one saying nothing of either", commands.add("close", [this]() { done.push_back("close"); })); + UNSCOPED_INFO("registered"); + CHECK(commands.add("save", [this]() { done.push_back("save"); }, [&can]() { return can; })); + UNSCOPED_INFO("and a toggle"); + CHECK(commands.add("word_wrap", [&wrap]() { wrap = !wrap; }, nullptr, [&wrap]() { return wrap; })); + UNSCOPED_INFO("and one saying nothing of either"); + CHECK(commands.add("close", [this]() { done.push_back("close"); })); - ensure("known", commands.has("save") && commands.has("word_wrap") && commands.has("close")); - ensure("not enabled while it says it cannot", !commands.enabled("save")); + REQUIRE(commands.has("save")); + REQUIRE(commands.has("word_wrap")); + REQUIRE(commands.has("close")); + UNSCOPED_INFO("not enabled while it says it cannot"); + CHECK_FALSE(commands.enabled("save")); can = true; - ensure("enabled once it can", commands.enabled("save")); - ensure("one saying nothing, always", commands.enabled("close")); + UNSCOPED_INFO("enabled once it can"); + CHECK(commands.enabled("save")); + UNSCOPED_INFO("one saying nothing, always"); + CHECK(commands.enabled("close")); commands.run("save"); commands.run("close"); - ensure("each run", done == std::vector{ "save", "close" }); + CHECK(done == std::vector{ "save", "close" }); - ensure("the toggle on", commands.checked("word_wrap")); + UNSCOPED_INFO("the toggle on"); + CHECK(commands.checked("word_wrap")); commands.run("word_wrap"); - ensure("and off", !commands.checked("word_wrap") && !wrap); - ensure("no toggle is off", !commands.checked("save")); - ensure("nothing unknown asked", commands.unknownAsked().empty()); + { + INFO("and off"); + CHECK_FALSE(commands.checked("word_wrap")); + CHECK_FALSE(wrap); + } + UNSCOPED_INFO("no toggle is off"); + CHECK_FALSE(commands.checked("save")); + UNSCOPED_INFO("nothing unknown asked"); + REQUIRE(commands.unknownAsked().empty()); } - template<> template<> - void alscriptstudiocommands_object::test<2>() + TEST_CASE_METHOD(alscriptstudiocommands_data, "alscriptstudiocommands: a name registers once and keeps its first command", "[alscriptstudiocommands]") { - set_test_name("a name is registered once, and keeps what it was registered with first"); - ensure("the first", commands.add("save", [this]() { done.push_back("first"); })); - ensure("the second refused", !commands.add("save", [this]() { done.push_back("second"); })); - ensure("unlisted the same", !commands.addUnlisted("save", [this]() { done.push_back("third"); })); + CHECK(commands.add("save", [this]() { done.push_back("first"); })); + UNSCOPED_INFO("the second refused"); + CHECK_FALSE(commands.add("save", [this]() { done.push_back("second"); })); + UNSCOPED_INFO("unlisted the same"); + CHECK_FALSE(commands.addUnlisted("save", [this]() { done.push_back("third"); })); commands.run("save"); - ensure("the first's", done == std::vector{ "first" }); - ensure("an unlisted one is a command like any", commands.addUnlisted("reveal", [this]() { done.push_back("reveal"); })); + UNSCOPED_INFO("the first's"); + CHECK(done == std::vector{ "first" }); + UNSCOPED_INFO("an unlisted one is a command like any"); + CHECK(commands.addUnlisted("reveal", [this]() { done.push_back("reveal"); })); commands.run("reveal"); - ensure_equals("run", done.back(), std::string("reveal")); + CHECK(done.back() == std::string("reveal")); std::vector names = commands.names(); std::sort(names.begin(), names.end()); - ensure("every name, once", names == std::vector{ "reveal", "save" }); + UNSCOPED_INFO("every name, once"); + CHECK(names == std::vector{ "reveal", "save" }); } - template<> template<> - void alscriptstudiocommands_object::test<3>() + TEST_CASE_METHOD(alscriptstudiocommands_data, "alscriptstudiocommands: an unknown name does nothing and is reported once", "[alscriptstudiocommands]") { - set_test_name("a name nothing registered does nothing, cannot be done, is off, and is said once"); commands.run("no_such"); - ensure("not enabled", !commands.enabled("no_such")); - ensure("off", !commands.checked("no_such")); - ensure("not done by a key", !commands.runIfEnabled("no_such")); - ensure("nor known", !commands.has("no_such")); - ensure("said once however often asked", commands.unknownAsked() == std::vector{ "no_such" }); + CHECK_FALSE(commands.enabled("no_such")); + CHECK_FALSE(commands.checked("no_such")); + UNSCOPED_INFO("not done by a key"); + CHECK_FALSE(commands.runIfEnabled("no_such")); + REQUIRE_FALSE(commands.has("no_such")); + UNSCOPED_INFO("said once however often asked"); + CHECK(commands.unknownAsked() == std::vector{ "no_such" }); commands.enabled("another"); - ensure("and another once more", commands.unknownAsked() == std::vector{ "no_such", "another" }); + UNSCOPED_INFO("and another once more"); + CHECK(commands.unknownAsked() == std::vector{ "no_such", "another" }); } - template<> template<> - void alscriptstudiocommands_object::test<4>() + TEST_CASE_METHOD(alscriptstudiocommands_data, "alscriptstudiocommands: a key or an ex command does a command only where it can be done now", "[alscriptstudiocommands]") { - set_test_name("a key or an ex command does a command only where it can be done now"); bool can = false; commands.add("format", [this]() { done.push_back("format"); }, [&can]() { return can; }); - ensure("not while it cannot", !commands.runIfEnabled("format") && done.empty()); + { + INFO("not while it cannot"); + CHECK_FALSE(commands.runIfEnabled("format")); + REQUIRE(done.empty()); + } can = true; - ensure("once it can", commands.runIfEnabled("format") && done == std::vector{ "format" }); + { + INFO("once it can"); + CHECK(commands.runIfEnabled("format")); + CHECK(done == std::vector{ "format" }); + } } - template<> template<> - void alscriptstudiocommands_object::test<5>() + TEST_CASE_METHOD(alscriptstudiocommands_data, "alscriptstudiocommands: a command may register others as it runs", "[alscriptstudiocommands]") { - set_test_name("a command may register others as it runs"); commands.add("first", [this]() { // Enough to move the table's own storage about. for (int i = 0; i < 64; ++i) @@ -124,8 +141,10 @@ namespace tut done.push_back("first"); }); commands.run("first"); - ensure("ran to its end", done == std::vector{ "first" }); + UNSCOPED_INFO("ran to its end"); + CHECK(done == std::vector{ "first" }); commands.run("made_63"); - ensure_equals("and what it made runs", done.back(), std::string("made")); + UNSCOPED_INFO("and what it made runs"); + CHECK(done.back() == std::string("made")); } } diff --git a/indra/newview/tests/alscriptstudiocomparepairs_test.cpp b/indra/newview/tests/alscriptstudiocomparepairs_test.cpp index 07a9aeda66..1c47b0a435 100644 --- a/indra/newview/tests/alscriptstudiocomparepairs_test.cpp +++ b/indra/newview/tests/alscriptstudiocomparepairs_test.cpp @@ -31,7 +31,7 @@ #include "alluauservice.h" #include "alscriptstudio_fixture.h" -#include "../test/lltut.h" +#include namespace { @@ -77,7 +77,7 @@ namespace const std::string RIGHT = "function c()\n x = 1\n y = 2\n z = 3\nend\nfunction a()\n q = 9\n r = 8\nend\nfunction b()\n p = 1\nend"; } -namespace tut +namespace { struct alscriptstudiocomparepairs_data { @@ -91,7 +91,7 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = window.services().addDoc(id, ALScriptRef(LLUUID::generateNewID(), LLUUID::generateNewID()), id); doc.loaded = true; @@ -122,64 +122,97 @@ namespace tut } }; - typedef test_group alscriptstudiocomparepairs_group; - typedef alscriptstudiocomparepairs_group::object alscriptstudiocomparepairs_object; - tut::alscriptstudiocomparepairs_group alscriptstudiocomparepairs_test("alscriptstudiocomparepairs"); - - template<> template<> - void alscriptstudiocomparepairs_object::test<1>() + TEST_CASE_METHOD(alscriptstudiocomparepairs_data, "alscriptstudiocomparepairs: each text's outline is asked for and functions pair once both answer", "[alscriptstudiocomparepairs]") { - set_test_name("each text's outline asked for as the two are set, from its parse; the functions paired once both are answered"); Doc& doc = tab("tab"); doc.compareView->setTexts(LEFT, RIGHT); - ensure_equals("both asked", studio.asked.size(), size_t(2)); + UNSCOPED_INFO("both asked"); + REQUIRE(studio.asked.size() == size_t(2)); const ALScriptAnalysis::Request& left = studio.asked[0].request; - ensure("the left's shape, as SLua, in front", left.kind == ALScriptAnalysis::Kind::Shape && left.lua && left.front && *left.text == LEFT && - left.id != studio.asked[1].request.id); + { + INFO("the left's shape, as SLua, in front"); + CHECK(left.kind == ALScriptAnalysis::Kind::Shape); + CHECK(left.lua); + CHECK(left.front); + CHECK(*left.text == LEFT); + CHECK(left.id != studio.asked[1].request.id); + } studio.answer(0); - ensure("one answered: nothing yet", doc.compareView->model().pairs().empty() && beside(doc, 0, 0)); + { + INFO("one answered: nothing yet"); + CHECK(doc.compareView->model().pairs().empty()); + CHECK(beside(doc, 0, 0)); + } studio.answerAll(); - ensure_equals("a with a, b with b", said(doc.compareView->model().pairs()), std::string("[0-4 5-8][5-7 9-11]")); - ensure("lined up", beside(doc, 0, 5) && beside(doc, 4, 8)); + UNSCOPED_INFO("a with a, b with b"); + CHECK(said(doc.compareView->model().pairs()) == std::string("[0-4 5-8][5-7 9-11]")); + { + INFO("lined up"); + CHECK(beside(doc, 0, 5)); + CHECK(beside(doc, 4, 8)); + } } - template<> template<> - void alscriptstudiocomparepairs_object::test<2>() + // The pairs are carried while the side typed in is asked about. + TEST_CASE_METHOD(alscriptstudiocomparepairs_data, "alscriptstudiocomparepairs: a side typed in is asked again alone and a stale answer dropped", "[alscriptstudiocomparepairs]") { - set_test_name("typed in: the right's asked again alone, an answer for a text gone dropped, the pairs carried meanwhile; the left stepped: its own"); Doc& doc = tab("tab"); doc.compareView->setTexts(LEFT, RIGHT); studio.answerAll(); doc.compareView->setRightText("-- a note\n" + RIGHT); - ensure("the right alone asked", studio.asked.size() == 1 && studio.asked[0].request.id.find("right") != std::string::npos); - ensure("carried meanwhile", said(doc.compareView->model().pairs()) == "[0-4 6-9][5-7 10-12]" && beside(doc, 0, 6)); + { + INFO("the right alone asked"); + REQUIRE(studio.asked.size() == 1); + CHECK(studio.asked[0].request.id.find("right") != std::string::npos); + } + { + INFO("carried meanwhile"); + CHECK(said(doc.compareView->model().pairs()) == "[0-4 6-9][5-7 10-12]"); + CHECK(beside(doc, 0, 6)); + } const std::string again = "-- a note\n-- and another\nfunction b()\n p = 1\nend\n" + RIGHT; doc.compareView->setRightText(again); - ensure_equals("asked again", studio.asked.size(), size_t(2)); - ensure("numbered on", studio.asked[1].request.version > studio.asked[0].request.version); + UNSCOPED_INFO("asked again"); + REQUIRE(studio.asked.size() == size_t(2)); + UNSCOPED_INFO("numbered on"); + CHECK(studio.asked[1].request.version > studio.asked[0].request.version); studio.answer(0); - ensure_equals("the answer for the text gone dropped", said(doc.compareView->model().pairs()), std::string("[0-4 10-13][5-7 14-16]")); + UNSCOPED_INFO("the answer for the text gone dropped"); + CHECK(said(doc.compareView->model().pairs()) == std::string("[0-4 10-13][5-7 14-16]")); studio.answer(0); - ensure_equals("the answer now: the first b with the first", said(doc.compareView->model().pairs()), std::string("[0-4 10-13][5-7 2-4]")); + UNSCOPED_INFO("the answer now: the first b with the first"); + CHECK(said(doc.compareView->model().pairs()) == std::string("[0-4 10-13][5-7 2-4]")); doc.compareView->setLeftText("-- left\n" + LEFT); - ensure("the left alone asked", studio.asked.size() == 1 && studio.asked[0].request.id.find("left") != std::string::npos); + { + INFO("the left alone asked"); + REQUIRE(studio.asked.size() == 1); + CHECK(studio.asked[0].request.id.find("left") != std::string::npos); + } studio.answerAll(); - ensure_equals("paired again", said(doc.compareView->model().pairs()), std::string("[1-5 10-13][6-8 2-4]")); + UNSCOPED_INFO("paired again"); + CHECK(said(doc.compareView->model().pairs()) == std::string("[1-5 10-13][6-8 2-4]")); } - template<> template<> - void alscriptstudiocomparepairs_object::test<3>() + TEST_CASE_METHOD(alscriptstudiocomparepairs_data, "alscriptstudiocomparepairs: texts known already pair at once and ranges or a notecard ask nothing", "[alscriptstudiocomparepairs]") { - set_test_name("texts known already paired at once; none asked where there are ranges, nor for a notecard"); Doc& doc = tab("tab"); doc.compareView->setTexts(LEFT, RIGHT); studio.answerAll(); doc.compareView->setTexts(LEFT, RIGHT); - ensure("none asked, paired at once", studio.asked.empty() && !doc.compareView->model().pairs().empty()); + { + INFO("none asked, paired at once"); + CHECK(studio.asked.empty()); + CHECK_FALSE(doc.compareView->model().pairs().empty()); + } doc.compareView->setTexts(LEFT + "\n", RIGHT, { { 0, 0, 0, 0 } }); - ensure("ranges: none asked", studio.asked.empty() && doc.compareView->model().pairs().empty()); + { + INFO("ranges: none asked"); + CHECK(studio.asked.empty()); + CHECK(doc.compareView->model().pairs().empty()); + } Doc& card = tab("card", true); card.compareView->setTexts(LEFT, RIGHT); - ensure("a notecard: none asked", studio.asked.empty()); + UNSCOPED_INFO("a notecard: none asked"); + CHECK(studio.asked.empty()); } } diff --git a/indra/newview/tests/alscriptstudiocomparewith_test.cpp b/indra/newview/tests/alscriptstudiocomparewith_test.cpp index 93bb65a496..fe46751c14 100644 --- a/indra/newview/tests/alscriptstudiocomparewith_test.cpp +++ b/indra/newview/tests/alscriptstudiocomparewith_test.cpp @@ -32,7 +32,7 @@ #include "alscriptstudio_fixture.h" #include "fsyspath.h" -#include "../test/lltut.h" +#include #include #include @@ -127,7 +127,7 @@ namespace }; } -namespace tut +namespace { struct alscriptstudiocomparewith_data { @@ -175,11 +175,12 @@ namespace tut return doc; } // The tab's text changed since it was saved. - void change(Doc& doc, const std::string& text) + void require_changed(Doc& doc, const std::string& text) { doc.editor->selectAll(); doc.editor->insertText(text); - ensure("changed since saved", doc.editor->isDirty()); + INFO("changed since saved"); + REQUIRE(doc.editor->isDirty()); } // A file in the test's folder, written as it is given. std::string file(const std::string& name, const std::string& bytes) @@ -191,202 +192,210 @@ namespace tut } }; - typedef test_group alscriptstudiocomparewith_group; - typedef alscriptstudiocomparewith_group::object alscriptstudiocomparewith_object; - tut::alscriptstudiocomparewith_group alscriptstudiocomparewith_test("alscriptstudiocomparewith"); - - template<> template<> - void alscriptstudiocomparewith_object::test<1>() + TEST_CASE_METHOD(alscriptstudiocomparewith_data, "alscriptstudiocomparewith: a saved tab alone is offered a file and an unloaded one nothing", "[alscriptstudiocomparewith]") { - set_test_name("a tab alone, as saved, is offered a file; one not loaded is offered nothing"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door", "default {}\n"); - ensure("it may be compared", ALScriptStudioCompareWith::canCompare(doc)); + CHECK(ALScriptStudioCompareWith::canCompare(doc)); unit->show(doc); - ensure_equals("asked once", studio.picks, 1); - ensure_equals("a file, and nothing else", listed(studio.values()), listed(Names{ "file" })); - ensure_equals("labelled", studio.candidates[0].label, said("CompareWithFile")); + UNSCOPED_INFO("asked once"); + CHECK(studio.picks == 1); + REQUIRE(listed(studio.values()) == listed(Names{ "file" })); + CHECK(studio.candidates[0].label == said("CompareWithFile")); LLStringUtil::format_map_t args; args["[NAME]"] = "door"; - ensure_equals("titled by the tab", studio.picked, said("CompareWithTitle", args)); - ensure_equals("and its hint", studio.hint, said("CompareWithPlaceholder")); + UNSCOPED_INFO("titled by the tab"); + CHECK(studio.picked == said("CompareWithTitle", args)); + CHECK(studio.hint == said("CompareWithPlaceholder")); Doc& loading = tab("lamp", "", false); - ensure("not before it has loaded", !ALScriptStudioCompareWith::canCompare(loading)); + UNSCOPED_INFO("not before it has loaded"); + CHECK_FALSE(ALScriptStudioCompareWith::canCompare(loading)); unit->show(loading); - ensure_equals("not asked", studio.picks, 1); + UNSCOPED_INFO("not asked"); + CHECK(studio.picks == 1); } - template<> template<> - void alscriptstudiocomparewith_object::test<2>() + TEST_CASE_METHOD(alscriptstudiocomparewith_data, "alscriptstudiocomparewith: a changed tab is offered its saved text unless no step reaches it", "[alscriptstudiocomparewith]") { - set_test_name("its text as last saved offered once it has changed, and chosen beside it; said where no step reaches it"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door", "as saved\n"); - change(doc, "as changed\n"); + require_changed(doc, "as changed\n"); unit->show(doc); - ensure_equals("first", studio.values().front(), std::string("saved")); - ensure_equals("labelled", studio.candidates[0].label, said("CompareSaved")); + CHECK(studio.values().front() == std::string("saved")); + CHECK(studio.candidates[0].label == said("CompareSaved")); studio.choose("saved"); - ensure_equals("set beside", listed(studio.did), listed(Names{ "compare door: as saved\n | as changed\n (" + said("CompareSaved") + " | )" })); + CHECK(listed(studio.did) == listed(Names{ "compare door: as saved\n | as changed\n (" + said("CompareSaved") + " | )" })); // A change after an undo past the save: nothing reaches it. doc.editor->undoJournal().markNeverSaved(); studio.choose("saved"); - ensure_equals("not compared", studio.did.size(), 1U); - ensure_equals("said", services().statuses.back(), said("CompareNothingSaved")); - ensure("as a failure", services().statusFailures.back()); + UNSCOPED_INFO("not compared"); + CHECK(studio.did.size() == 1U); + CHECK(services().statuses.back() == said("CompareNothingSaved")); + UNSCOPED_INFO("as a failure"); + CHECK(services().statusFailures.back()); } - template<> template<> - void alscriptstudiocomparewith_object::test<3>() + TEST_CASE_METHOD(alscriptstudiocomparewith_data, "alscriptstudiocomparewith: what was saved over it elsewhere is offered until it is settled", "[alscriptstudiocomparewith]") { - set_test_name("what was saved over it elsewhere offered where a save came up against it"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door", "mine\n"); unit->show(doc); - ensure("not where none did", studio.row("saved_there").value.empty()); + UNSCOPED_INFO("not where none did"); + CHECK(studio.row("saved_there").value.empty()); doc.savedThere = "theirs\n"; unit->show(doc); - ensure_equals("offered", studio.row("saved_there").label, said("CompareSavedThere")); + CHECK(studio.row("saved_there").label == said("CompareSavedThere")); studio.choose("saved_there"); - ensure_equals("set beside", listed(studio.did), listed(Names{ "compare door: theirs\n | mine\n (" + said("CompareSavedThere") + " | )" })); + CHECK(listed(studio.did) == listed(Names{ "compare door: theirs\n | mine\n (" + said("CompareSavedThere") + " | )" })); // Settled since the list was made: nothing, and said. doc.savedThere.reset(); studio.choose("saved_there"); - ensure_equals("nothing more", studio.did.size(), 1U); - ensure_equals("said", services().statuses.back(), said("CompareSavedThereGone")); - ensure("as a failure", services().statusFailures.back()); + UNSCOPED_INFO("nothing more"); + CHECK(studio.did.size() == 1U); + CHECK(services().statuses.back() == said("CompareSavedThereGone")); + UNSCOPED_INFO("as a failure"); + CHECK(services().statusFailures.back()); } - template<> template<> - void alscriptstudiocomparewith_object::test<4>() + TEST_CASE_METHOD(alscriptstudiocomparewith_data, "alscriptstudiocomparewith: other loaded tabs are offered in order and compared as they are now", "[alscriptstudiocomparewith]") { - set_test_name("every other tab loaded offered in the strip's order, said unsaved where it is; chosen as it is now"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& door = tab("door", "door\n"); Doc& lamp = tab("lamp", "lamp as saved\n"); - change(lamp, "lamp now\n"); + require_changed(lamp, "lamp now\n"); tab("gate", "", false); Doc& sign = tab("sign", "sign\n"); unit->show(door); - ensure_equals("the others loaded, in order", listed(studio.values()), listed(Names{ "tab:lamp", "tab:sign", "file" })); - ensure_equals("by name", studio.row("tab:lamp").label, std::string("lamp")); - ensure_equals("unsaved", studio.row("tab:lamp").detail, said("CompareWithTabUnsaved")); - ensure_equals("saved", studio.row("tab:sign").detail, said("CompareWithTab")); + UNSCOPED_INFO("the others loaded, in order"); + CHECK(listed(studio.values()) == listed(Names{ "tab:lamp", "tab:sign", "file" })); + CHECK(studio.row("tab:lamp").label == std::string("lamp")); + CHECK(studio.row("tab:lamp").detail == said("CompareWithTabUnsaved")); + CHECK(studio.row("tab:sign").detail == said("CompareWithTab")); // Typed since the list was made: what is set beside is the tab now. - change(lamp, "lamp later\n"); + require_changed(lamp, "lamp later\n"); studio.choose("tab:lamp"); LLStringUtil::format_map_t unsaved; unsaved["[TITLE]"] = "lamp"; // Titled as a copy of it as it was, which stays as it is. const auto snapshot = [this](const std::string& name) { return this->said("CompareTabSnapshot", { { "[NAME]", name } }); }; - ensure_equals("unsaved, as it is now", studio.did.back(), - "compare door: lamp later\n | door\n (" + snapshot(said("CompareUnsaved", unsaved)) + " | )"); + UNSCOPED_INFO("unsaved, as it is now"); + CHECK(studio.did.back() == + "compare door: lamp later\n | door\n (" + snapshot(said("CompareUnsaved", unsaved)) + " | )"); studio.choose("tab:sign"); - ensure_equals("saved, by its name", studio.did.back(), "compare door: sign\n | door\n (" + snapshot("sign") + " | )"); + UNSCOPED_INFO("saved, by its name"); + CHECK(studio.did.back() == "compare door: sign\n | door\n (" + snapshot("sign") + " | )"); // Closed while the list was up: nothing, and said, by the name it // had in the list. sign.loaded = false; services().docs.erase(services().docs.begin() + 3); studio.choose("tab:sign"); - ensure_equals("nothing more", studio.did.size(), 2U); - ensure_equals("said", services().statuses.back(), said("CompareTabClosed", { { "[NAME]", "sign" } })); - ensure("as a failure", services().statusFailures.back()); + UNSCOPED_INFO("nothing more"); + CHECK(studio.did.size() == 2U); + CHECK(services().statuses.back() == said("CompareTabClosed", { { "[NAME]", "sign" } })); + UNSCOPED_INFO("as a failure"); + CHECK(services().statusFailures.back()); } - template<> template<> - void alscriptstudiocomparewith_object::test<5>() + TEST_CASE_METHOD(alscriptstudiocomparewith_data, "alscriptstudiocomparewith: the clipboard is offered with its line count when it holds text", "[alscriptstudiocomparewith]") { - set_test_name("the clipboard offered with how many lines it holds, where it holds text; chosen as it holds now"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door", "door\n"); unit->show(doc); - ensure("not without text", studio.row("clipboard").value.empty()); + UNSCOPED_INFO("not without text"); + CHECK(studio.row("clipboard").value.empty()); studio.clipboard = std::string(); unit->show(doc); - ensure("not with none", studio.row("clipboard").value.empty()); + UNSCOPED_INFO("not with none"); + CHECK(studio.row("clipboard").value.empty()); studio.clipboard = "one\ntwo\n"; unit->show(doc); - ensure_equals("labelled", studio.row("clipboard").label, said("CompareClipboard")); - ensure_equals("two lines", studio.row("clipboard").detail, services().counted("CompareWithLines", 2)); + CHECK(studio.row("clipboard").label == said("CompareClipboard")); + CHECK(studio.row("clipboard").detail == services().counted("CompareWithLines", 2)); studio.clipboard = "one\ntwo\nthree"; unit->show(doc); - ensure_equals("a last line without a break counted", studio.row("clipboard").detail, services().counted("CompareWithLines", 3)); + UNSCOPED_INFO("a last line without a break counted"); + CHECK(studio.row("clipboard").detail == services().counted("CompareWithLines", 3)); studio.clipboard = "copied since\n"; studio.choose("clipboard"); - ensure_equals("as it holds now", listed(studio.did), listed(Names{ "compare door: copied since\n | door\n (" + said("CompareClipboard") + " | )" })); + UNSCOPED_INFO("as it holds now"); + CHECK(listed(studio.did) == listed(Names{ "compare door: copied since\n | door\n (" + said("CompareClipboard") + " | )" })); studio.clipboard.reset(); studio.choose("clipboard"); - ensure_equals("not once emptied", studio.did.size(), 1U); - ensure_equals("said", services().statuses.back(), said("CompareWithClipboardEmpty")); + UNSCOPED_INFO("not once emptied"); + CHECK(studio.did.size() == 1U); + CHECK(services().statuses.back() == said("CompareWithClipboardEmpty")); } - template<> template<> - void alscriptstudiocomparewith_object::test<6>() + TEST_CASE_METHOD(alscriptstudiocomparewith_data, "alscriptstudiocomparewith: a picked file is compared with LF line breaks or said unreadable", "[alscriptstudiocomparewith]") { - set_test_name("a file picked is read with its line breaks as an editor keeps them and set beside under its name; said where unreadable"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door", "door\n"); unit->show(doc); studio.choose("file"); - ensure_equals("one file asked for", studio.filesAsked, 1); - ensure("only one", !studio.manyAsked); + UNSCOPED_INFO("one file asked for"); + CHECK(studio.filesAsked == 1); + UNSCOPED_INFO("only one"); + CHECK_FALSE(studio.manyAsked); studio.files({}); - ensure("none picked, nothing", studio.did.empty()); + UNSCOPED_INFO("none picked, nothing"); + CHECK(studio.did.empty()); const std::string path = file("other.lsl", "one\r\ntwo\r\n"); studio.files({ path }); - ensure_equals("set beside", listed(studio.did), listed(Names{ "compare door: one\ntwo\n | door\n (other.lsl | )" })); + UNSCOPED_INFO("its line breaks as an editor keeps them"); + CHECK(listed(studio.did) == listed(Names{ "compare door: one\ntwo\n | door\n (other.lsl | )" })); unit->show(doc); studio.choose("file"); const std::string missing = folder + "/missing.lsl"; studio.files({ missing }); - ensure_equals("not compared", studio.did.size(), 1U); + UNSCOPED_INFO("not compared"); + CHECK(studio.did.size() == 1U); LLStringUtil::format_map_t args; args["[FILE]"] = missing; - ensure_equals("said", services().statuses.back(), said("LoadFromFileFailed", args)); - ensure("as a failure", services().statusFailures.back()); + CHECK(services().statuses.back() == said("LoadFromFileFailed", args)); + UNSCOPED_INFO("as a failure"); + CHECK(services().statusFailures.back()); // The tab closed while the files were picked: nothing. unit->show(doc); studio.choose("file"); services().docs.clear(); studio.files({ path }); - ensure_equals("nothing more", studio.did.size(), 1U); + UNSCOPED_INFO("nothing more"); + CHECK(studio.did.size() == 1U); } - template<> template<> - void alscriptstudiocomparewith_object::test<7>() + TEST_CASE_METHOD(alscriptstudiocomparewith_data, "alscriptstudiocomparewith: recent files but the tab's own are offered by name and folder", "[alscriptstudiocomparewith]") { - set_test_name("the files opened lately offered by name and folder, but the tab's own; one chosen set beside"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door", "door\n"); const std::string own = file("door.lsl", "door\n"); @@ -394,46 +403,45 @@ namespace tut doc.file = own; studio.recent = { own, other }; unit->show(doc); - ensure_equals("the other alone", listed(studio.values()), listed(Names{ "file", "recent:0" })); - ensure_equals("by name", studio.row("recent:0").label, std::string("lamp.lsl")); - ensure_equals("and folder", studio.row("recent:0").detail, gDirUtilp->getDirName(other)); + UNSCOPED_INFO("the other alone"); + CHECK(listed(studio.values()) == listed(Names{ "file", "recent:0" })); + CHECK(studio.row("recent:0").label == std::string("lamp.lsl")); + CHECK(studio.row("recent:0").detail == gDirUtilp->getDirName(other)); studio.choose("recent:0"); - ensure_equals("set beside", listed(studio.did), listed(Names{ "compare door: lamp\n | door\n (lamp.lsl | )" })); + CHECK(listed(studio.did) == listed(Names{ "compare door: lamp\n | door\n (lamp.lsl | )" })); } - template<> template<> - void alscriptstudiocomparewith_object::test<8>() + TEST_CASE_METHOD(alscriptstudiocomparewith_data, "alscriptstudiocomparewith: local history is offered with its save count when it has any", "[alscriptstudiocomparewith]") { - set_test_name("its local history offered with how many saves are kept, where any are; chosen, its saves listed"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door", "door\n"); unit->show(doc); - ensure("not with none kept", studio.row("history").value.empty()); + UNSCOPED_INFO("not with none kept"); + CHECK(studio.row("history").value.empty()); for (const char* text : { "first", "second" }) { ALSavedText one; one.key = ALScriptStudioHistory::keyOf(doc); one.text = text; one.when = LLDate::now(); - ensure("kept", history->keep(one)); + CAPTURE(text); + REQUIRE(history->keep(one)); } unit->show(doc); - ensure_equals("labelled", studio.row("history").label, said("CompareWithHistory")); - ensure_equals("two kept", studio.row("history").detail, services().counted("CompareWithSaves", 2)); + CHECK(studio.row("history").label == said("CompareWithHistory")); + CHECK(studio.row("history").detail == services().counted("CompareWithSaves", 2)); studio.choose("history"); - ensure_equals("listed", listed(studio.did), listed(Names{ "history door" })); + CHECK(listed(studio.did) == listed(Names{ "history door" })); } - template<> template<> - void alscriptstudiocomparewith_object::test<9>() + TEST_CASE_METHOD(alscriptstudiocomparewith_data, "alscriptstudiocomparewith: items like it are offered last by name and place and found by place", "[alscriptstudiocomparewith]") { - set_test_name("the items like it in objects in hand offered by name and place, last; one chosen loaded under both"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } const std::string sep = ALScriptExplorerModel::PLACE_SEPARATOR; Doc& doc = tab("door", "door\n"); @@ -441,32 +449,36 @@ namespace tut Item{ ALScriptRef(LLUUID::generateNewID(), LLUUID::generateNewID()), "door", "" } }; studio.clipboard = "x"; unit->show(doc); - ensure_equals("after the rest", listed(studio.values()), listed(Names{ "clipboard", "file", "item:0", "item:1" })); - ensure_equals("by name", studio.row("item:0").label, std::string("door")); - ensure_equals("and place", studio.row("item:0").detail, "House" + sep + "Front"); + UNSCOPED_INFO("after the rest"); + CHECK(listed(studio.values()) == listed(Names{ "clipboard", "file", "item:0", "item:1" })); + CHECK(studio.row("item:0").label == std::string("door")); + CHECK(studio.row("item:0").detail == "House" + sep + "Front"); studio.choose("item:0"); studio.choose("item:1"); - ensure_equals("each under its place and name", listed(studio.did), - listed(Names{ "item door: door (House" + sep + "Front" + sep + "door)", "item door: door (door)" })); + UNSCOPED_INFO("each under its place and name"); + CHECK(listed(studio.did) == + listed(Names{ "item door: door (House" + sep + "Front" + sep + "door)", "item door: door (door)" })); // Its place is what is typed to find it. const std::vector ranked = ALQuickOpen::rank(studio.candidates, "front"); - ensure("found by its place", !ranked.empty() && studio.candidates[ranked.front()].value == "item:0"); + { + INFO("found by its place"); + REQUIRE_FALSE(ranked.empty()); + CHECK(studio.candidates[ranked.front()].value == "item:0"); + } } - template<> template<> - void alscriptstudiocomparewith_object::test<10>() + TEST_CASE_METHOD(alscriptstudiocomparewith_data, "alscriptstudiocomparewith: a choice made after the window has gone does nothing", "[alscriptstudiocomparewith]") { - set_test_name("a choice made after the window has gone does nothing"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door", "door\n"); studio.clipboard = "copied\n"; unit->show(doc); unit.reset(); studio.choose("clipboard"); - ensure("nothing", studio.did.empty()); + CHECK(studio.did.empty()); } } diff --git a/indra/newview/tests/alscriptstudiodoc_test.cpp b/indra/newview/tests/alscriptstudiodoc_test.cpp index a906e5f2e9..5fe96f5b05 100644 --- a/indra/newview/tests/alscriptstudiodoc_test.cpp +++ b/indra/newview/tests/alscriptstudiodoc_test.cpp @@ -32,7 +32,7 @@ #include "alscriptstudio_fixture.h" -#include "../test/lltut.h" +#include #include #include @@ -58,7 +58,7 @@ namespace }; } -namespace tut +namespace { struct alscriptstudiodoc_data { @@ -71,55 +71,60 @@ namespace tut return one; } }; - typedef test_group alscriptstudiodoc_group; - typedef alscriptstudiodoc_group::object alscriptstudiodoc_object; - alscriptstudiodoc_group alscriptstudiodoc_instance("ALScriptStudioDoc"); - template<> template<> - void alscriptstudiodoc_object::test<1>() + TEST_CASE_METHOD(alscriptstudiodoc_data, "ALScriptStudioDoc: what counts as unsaved and when a save is under way", "[ALScriptStudioDoc]") { - set_test_name("unsaved is the text, or a compile target or an experience picked; a save is under way at any stage of it, or while the preprocessor runs"); Doc doc; - ensure("nothing changed", !doc.unsaved()); + UNSCOPED_INFO("nothing changed"); + CHECK_FALSE(doc.unsaved()); doc.targetChosen = true; - ensure("a compile target picked", doc.unsaved()); + UNSCOPED_INFO("a compile target picked"); + CHECK(doc.unsaved()); doc.targetChosen = false; doc.experienceChosen = true; - ensure("an experience picked", doc.unsaved()); + UNSCOPED_INFO("an experience picked"); + CHECK(doc.unsaved()); - ensure("no save", !doc.saveUnderway()); + UNSCOPED_INFO("no save"); + CHECK_FALSE(doc.saveUnderway()); doc.preprocessing = true; - ensure("the preprocessor running, which a save may wait on", doc.saveUnderway()); + UNSCOPED_INFO("the preprocessor running, which a save may wait on"); + CHECK(doc.saveUnderway()); doc.preprocessing = false; ALScriptSaveFlow::Tab tab; tab.holdOnErrors = true; doc.save.route(tab); - ensure("waiting on a check", doc.saveUnderway()); + UNSCOPED_INFO("waiting on a check"); + CHECK(doc.saveUnderway()); doc.save.checked(); - ensure("and not once it has come", !doc.saveUnderway()); + UNSCOPED_INFO("and not once it has come"); + CHECK_FALSE(doc.saveUnderway()); doc.save.sent(ALTextUndo::SavePoint(), std::nullopt, {}); - ensure("sent", doc.saveUnderway()); + UNSCOPED_INFO("sent"); + CHECK(doc.saveUnderway()); } - template<> template<> - void alscriptstudiodoc_object::test<2>() + TEST_CASE_METHOD(alscriptstudiodoc_data, "ALScriptStudioDoc: the expansion is in front only where there is one", "[ALScriptStudioDoc]") { - set_test_name("the expansion is in front only where there is one"); Doc doc; doc.view = Doc::View::Expanded; - ensure("nothing expanded yet: the source", doc.shownView() == Doc::View::Source); - ensure("and its text", doc.shownText() == doc.editor); + UNSCOPED_INFO("nothing expanded yet: the source"); + CHECK(doc.shownView() == Doc::View::Source); + UNSCOPED_INFO("and its text"); + CHECK(doc.shownText() == doc.editor); } - template<> template<> - void alscriptstudiodoc_object::test<3>() + TEST_CASE_METHOD(alscriptstudiodoc_data, "ALScriptStudioDoc: levelOf reads compiler words and severities and ProblemTraits a row", "[ALScriptStudioDoc]") { - set_test_name("levels: the compiler's words, the checkers' severities, the marks, and how the findings store reads a problem"); - ensure("WARNING", Doc::levelOf("WARNING") == Doc::Level::Warning); - ensure("WARN", Doc::levelOf("WARN") == Doc::Level::Warning); - ensure("anything else is an error, as the server has two", Doc::levelOf("ERROR") == Doc::Level::Error && Doc::levelOf("oops") == Doc::Level::Error); - ensure("a note", Doc::levelOf(ALScriptProblem::Severity::Note) == Doc::Level::Note); - ensure("named", std::string(Doc::levelName(Doc::Level::Warning)) == "WARNING"); + CHECK(Doc::levelOf("WARNING") == Doc::Level::Warning); + CHECK(Doc::levelOf("WARN") == Doc::Level::Warning); + { + INFO("anything else is an error, as the server has two"); + CHECK(Doc::levelOf("ERROR") == Doc::Level::Error); + CHECK(Doc::levelOf("oops") == Doc::Level::Error); + } + CHECK(Doc::levelOf(ALScriptProblem::Severity::Note) == Doc::Level::Note); + CHECK(std::string(Doc::levelName(Doc::Level::Warning)) == "WARNING"); Doc::Shown shown; shown.level = Doc::Level::Warning; @@ -127,29 +132,34 @@ namespace tut shown.message = "unused local"; shown.fileName = "util.luau"; shown.fixes = { fix(ALScriptFix::Kind::Suppress) }; - ensure("a suppression is no fix", !Doc::ProblemTraits::fixable(shown)); + UNSCOPED_INFO("a suppression is no fix"); + CHECK_FALSE(Doc::ProblemTraits::fixable(shown)); shown.fixes.push_back(fix(ALScriptFix::Kind::Fix)); - ensure("a change is", Doc::ProblemTraits::fixable(shown)); - ensure("found by its level's name", Doc::ProblemTraits::mentions(shown, "warn")); - ensure("by its include", Doc::ProblemTraits::mentions(shown, "util")); - ensure("not by what it does not say", !Doc::ProblemTraits::mentions(shown, "state")); - ensure("its rule is where it came from", Doc::ProblemTraits::rule(shown) == "Luau"); - ensure("its level", Doc::ProblemTraits::level(shown) == ALFindingLevel::Warning); + UNSCOPED_INFO("a change is"); + CHECK(Doc::ProblemTraits::fixable(shown)); + UNSCOPED_INFO("found by its level's name"); + CHECK(Doc::ProblemTraits::mentions(shown, "warn")); + UNSCOPED_INFO("by its include"); + CHECK(Doc::ProblemTraits::mentions(shown, "util")); + UNSCOPED_INFO("not by what it does not say"); + CHECK_FALSE(Doc::ProblemTraits::mentions(shown, "state")); + UNSCOPED_INFO("its rule is where it came from"); + CHECK(Doc::ProblemTraits::rule(shown) == "Luau"); + CHECK(Doc::ProblemTraits::level(shown) == ALFindingLevel::Warning); } - template<> template<> - void alscriptstudiodoc_object::test<4>() + TEST_CASE_METHOD(alscriptstudiodoc_data, "ALScriptStudioDoc: the fake services say skin words and counts and find tabs", "[ALScriptStudioDoc]") { - set_test_name("the fake of the window's services: the studio's own words, counted, and its tabs found"); al_studio_test::StudioWindow window; if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } al_studio_test::FakeServices services(window.floater); - ensure_equals("a word of the skin's", services.words("ObjectNameComing"), std::string("\xE2\x80\xA6")); - ensure_equals("one", services.counted("Fixes", 1), std::string("1 fix")); - ensure_equals("many", services.counted("Fixes", 3), std::string("3 fixes")); + UNSCOPED_INFO("a word of the skin's"); + CHECK(services.words("ObjectNameComing") == std::string("\xE2\x80\xA6")); + CHECK(services.counted("Fixes", 1) == std::string("1 fix")); + CHECK(services.counted("Fixes", 3) == std::string("3 fixes")); LLUUID object, item; object.generate(); @@ -158,58 +168,92 @@ namespace tut Doc& script = services.addDoc("script", ref, "Door"); Doc& file = services.addDoc("disk:/lib.lsl"); file.file = "/lib.lsl"; - ensure("the first in front", services.frontDoc() == &script); - ensure("by id", services.findDoc("disk:/lib.lsl") == &file); - ensure("by what it holds", services.findDoc(ref) == &script); - ensure("none", services.findDoc("nothing") == nullptr); + UNSCOPED_INFO("the first in front"); + CHECK(services.frontDoc() == &script); + UNSCOPED_INFO("by id"); + CHECK(services.findDoc("disk:/lib.lsl") == &file); + UNSCOPED_INFO("by what it holds"); + CHECK(services.findDoc(ref) == &script); + CHECK(services.findDoc("nothing") == nullptr); services.report("Saved", false, &script, { "retry" }); - ensure("said of the tab", services.reports.size() == 1 && services.reports.front().doc == "script" && services.reports.front().actions.size() == 1); + { + INFO("said of the tab"); + REQUIRE(services.reports.size() == 1); + CHECK(services.reports.front().doc == "script"); + CHECK(services.reports.front().actions.size() == 1); + } services.openScript(ref, "Door", std::nullopt, 12); - ensure("asked to open at a line", services.opened.size() == 1 && services.opened.front().line == 12); + { + INFO("asked to open at a line"); + REQUIRE(services.opened.size() == 1); + CHECK(services.opened.front().line == 12); + } } - template<> template<> - void alscriptstudiodoc_object::test<5>() + TEST_CASE_METHOD(alscriptstudiodoc_data, "ALScriptStudioDoc: namedIn finds an include line or a require call and foundAs a run's find", "[ALScriptStudioDoc]") { - set_test_name("an include or a module a place names: anywhere on an #include line, or in a require call; and what a run found it as"); const ALTextDocument lsl("default {}\n # include \"lib/util.lsl\" // the helpers\n#include \n#define X \"y.lsl\"\n"); const std::vector none; const std::optional util = Doc::namedIn(lsl, ALTextPos(1, 0), false, none); - ensure("on its line, before the #", util.has_value() && util->name == "lib/util.lsl" && !util->require); - ensure("the whole line", util->range == ALTextRange(ALTextPos(1, 0), ALTextPos(1, lsl.lineLength(1)))); - ensure("angled", Doc::namedIn(lsl, ALTextPos(2, 12), false, none)->name == "sys.lsl"); - ensure("not a string elsewhere", - !Doc::namedIn(lsl, ALTextPos(3, 12), false, none) && !Doc::namedIn(lsl, ALTextPos(0, 3), false, none)); + { + INFO("on its line, before the #"); + REQUIRE(util.has_value()); + CHECK(util->name == "lib/util.lsl"); + CHECK_FALSE(util->require); + } + UNSCOPED_INFO("the whole line"); + CHECK(util->range == ALTextRange(ALTextPos(1, 0), ALTextPos(1, lsl.lineLength(1)))); + UNSCOPED_INFO("angled"); + CHECK(Doc::namedIn(lsl, ALTextPos(2, 12), false, none)->name == "sys.lsl"); + { + INFO("not a string elsewhere"); + CHECK_FALSE(Doc::namedIn(lsl, ALTextPos(3, 12), false, none)); + CHECK_FALSE(Doc::namedIn(lsl, ALTextPos(0, 3), false, none)); + } const std::string source = "local m = require(\"mod\")\nlocal n = 1\n"; const ALTextDocument lua(source); const std::vector calls = ALPreprocessor::requiresIn(source); const std::optional mod = Doc::namedIn(lua, ALTextPos(0, 12), true, calls); - ensure("in a require call", mod.has_value() && mod->name == "mod" && mod->require); - ensure("over the call", mod->range == ALTextRange(ALTextPos(0, 10), ALTextPos(0, 24))); - ensure("not before it", !Doc::namedIn(lua, ALTextPos(0, 3), true, calls)); - ensure("not in LSL", !Doc::namedIn(lua, ALTextPos(0, 12), false, calls)); + { + INFO("in a require call"); + REQUIRE(mod.has_value()); + CHECK(mod->name == "mod"); + CHECK(mod->require); + } + UNSCOPED_INFO("over the call"); + CHECK(mod->range == ALTextRange(ALTextPos(0, 10), ALTextPos(0, 24))); + UNSCOPED_INFO("not before it"); + CHECK_FALSE(Doc::namedIn(lua, ALTextPos(0, 3), true, calls)); + UNSCOPED_INFO("not in LSL"); + CHECK_FALSE(Doc::namedIn(lua, ALTextPos(0, 12), false, calls)); Doc doc; - ensure("no run, nothing found", doc.foundAs("lib/util.lsl").empty()); + UNSCOPED_INFO("no run, nothing found"); + CHECK(doc.foundAs("lib/util.lsl").empty()); doc.expanded.valid = true; doc.expanded.resolved = { { "", "lib/util.lsl", false, "disk:/s/lib/util.lsl" }, { "disk:/s/lib/util.lsl", "inner.lsl", false, "disk:/s/inner.lsl" }, { "", "mod", true, "inv:mod" } }; - ensure_equals("the script's own", doc.foundAs("lib/util.lsl"), std::string("disk:/s/lib/util.lsl")); - ensure("not an include's own", doc.foundAs("inner.lsl").empty()); - ensure("a module as a module", doc.foundAs("mod", true) == "inv:mod" && doc.foundAs("mod", false).empty()); + UNSCOPED_INFO("the script's own"); + CHECK(doc.foundAs("lib/util.lsl") == std::string("disk:/s/lib/util.lsl")); + UNSCOPED_INFO("not an include's own"); + CHECK(doc.foundAs("inner.lsl").empty()); + { + INFO("a module as a module"); + CHECK(doc.foundAs("mod", true) == "inv:mod"); + CHECK(doc.foundAs("mod", false).empty()); + } doc.expanded.valid = false; doc.uploaded.valid = true; doc.uploaded.resolved = { { "", "lib/util.lsl", false, "disk:/s/other.lsl" } }; - ensure_equals("or the last save's run", doc.foundAs("lib/util.lsl"), std::string("disk:/s/other.lsl")); + UNSCOPED_INFO("or the last save's run"); + CHECK(doc.foundAs("lib/util.lsl") == std::string("disk:/s/other.lsl")); } - template<> template<> - void alscriptstudiodoc_object::test<6>() + TEST_CASE_METHOD(alscriptstudiodoc_data, "ALScriptStudioDoc: run-time errors are held until the map is known then placed and counted", "[ALScriptStudioDoc]") { - set_test_name("a run-time error read back to the source, counted where said again; held until the map is known, then placed"); Doc doc; // Six lines run: the first two the include's, the rest the // script's from its first line on. @@ -230,34 +274,68 @@ namespace tut Doc::RuntimeProblem said; said.line = 4; said.message = "Math Error"; - ensure_equals("no map: as the region counts it", doc.placeOfRunning(4, -1).line, 4); + UNSCOPED_INFO("no map: as the region counts it"); + CHECK(doc.placeOfRunning(4, -1).line == 4); doc.heardRuntime(said, true); - ensure("held", doc.runtime.empty() && doc.runtimeHeld.size() == 1); + { + INFO("held"); + CHECK(doc.runtime.empty()); + CHECK(doc.runtimeHeld.size() == 1); + } doc.heardRuntime(said, true); doc.uploaded.valid = true; doc.uploaded.map = map; const Doc::RunningPlace place = doc.placeOfRunning(1, 3); - ensure("an include's line, by its path and name", place.line == 1 && place.column == 3 && place.file == "disk:/s/lib.lsl" && place.fileName == "lib.lsl"); + { + INFO("an include's line, by its path and name"); + CHECK(place.line == 1); + CHECK(place.column == 3); + CHECK(place.file == "disk:/s/lib.lsl"); + CHECK(place.fileName == "lib.lsl"); + } doc.placeHeldRuntime(); - ensure("placed, and let go of", doc.runtimeHeld.empty() && doc.runtime.size() == 1); - ensure("at the source's line, said twice", doc.runtime[0].line == 2 && doc.runtime[0].file.empty() && doc.runtime[0].count == 2); + { + INFO("placed, and let go of"); + CHECK(doc.runtimeHeld.empty()); + REQUIRE(doc.runtime.size() == 1); + } + { + INFO("at the source's line, said twice"); + CHECK(doc.runtime[0].line == 2); + CHECK(doc.runtime[0].file.empty()); + CHECK(doc.runtime[0].count == 2); + } doc.heardRuntime(said, false); - ensure_equals("said again: counted", doc.runtime[0].count, 3); + UNSCOPED_INFO("said again: counted"); + CHECK(doc.runtime[0].count == 3); said.line = 0; doc.heardRuntime(said, false); - ensure("in the include", doc.runtime.size() == 2 && doc.runtime[1].file == "disk:/s/lib.lsl" && doc.runtime[1].line == 0); + { + INFO("in the include"); + REQUIRE(doc.runtime.size() == 2); + CHECK(doc.runtime[1].file == "disk:/s/lib.lsl"); + CHECK(doc.runtime[1].line == 0); + } const Doc::RunningPlace made = doc.placeOfRunning(9, -1); - ensure("past the map: code the preprocessor made, at its own line", made.generated && made.line == 9 && made.file.empty()); + { + INFO("past the map: code the preprocessor made, at its own line"); + CHECK(made.generated); + CHECK(made.line == 9); + CHECK(made.file.empty()); + } said.line = 9; doc.heardRuntime(said, false); - ensure("said to be there", doc.runtime.size() == 3 && doc.runtime[2].file == Doc::GENERATED && doc.runtime[2].line == 9); + { + INFO("said to be there"); + REQUIRE(doc.runtime.size() == 3); + CHECK(doc.runtime[2].file == Doc::GENERATED); + CHECK(doc.runtime[2].line == 9); + } } - template<> template<> - void alscriptstudiodoc_object::test<7>() + TEST_CASE_METHOD(alscriptstudiodoc_data, "ALScriptStudioDoc: setShown counts errors and warnings but not notes", "[ALScriptStudioDoc]") { - set_test_name("the rows put in counted: errors and warnings, notes not; counted again when others are put in"); Doc doc; std::vector rows(4); rows[0].level = Doc::Level::Error; @@ -265,21 +343,21 @@ namespace tut rows[2].level = Doc::Level::Warning; rows[3].level = Doc::Level::Note; doc.setShown(rows); - ensure("four rows", doc.shown().size() == 4); - ensure_equals("one error", doc.shownErrors, 1); - ensure_equals("two warnings", doc.shownWarnings, 2); + CHECK(doc.shown().size() == 4); + CHECK(doc.shownErrors == 1); + CHECK(doc.shownWarnings == 2); doc.setShown({}); - ensure("none", doc.shown().empty() && doc.shownErrors == 0 && doc.shownWarnings == 0); + CHECK(doc.shown().empty()); + CHECK(doc.shownErrors == 0); + CHECK(doc.shownWarnings == 0); } - template<> template<> - void alscriptstudiodoc_object::test<8>() + TEST_CASE_METHOD(alscriptstudiodoc_data, "ALScriptStudioDoc: a notecard's items carry into another tab but a script tab has none", "[ALScriptStudioDoc]") { - set_test_name("a notecard's items carried into another tab; a script's tab carries none"); al_studio_test::StudioWindow window; if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } ALCodeEditor::Params p(LLUICtrlFactory::getDefaultParams()); p.name = "editor"; @@ -293,20 +371,24 @@ namespace tut card.items->loaded({ a, b }); Doc there; card.carryItemsTo(there); - ensure("carried", there.carriedEmbedded && there.carriedEmbedded->size() == 2 && (*there.carriedEmbedded)[1] == b); + { + INFO("carried"); + REQUIRE(there.carriedEmbedded); + REQUIRE(there.carriedEmbedded->size() == 2); + CHECK((*there.carriedEmbedded)[1] == b); + } Doc script, after; script.carryItemsTo(after); - ensure("a script's tab carries nothing", !after.carriedEmbedded); + UNSCOPED_INFO("a script's tab carries nothing"); + CHECK_FALSE(after.carriedEmbedded); } - template<> template<> - void alscriptstudiodoc_object::test<9>() + TEST_CASE_METHOD(alscriptstudiodoc_data, "ALScriptStudioDoc: undo follows the view in front but the tab's own for a comparison of it", "[ALScriptStudioDoc]") { - set_test_name("undo takes back the view in front's steps, but the tab's own while a comparison that follows the tab is in front"); al_studio_test::StudioWindow window; if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } ALCodeEditor::Params p(LLUICtrlFactory::getDefaultParams()); p.name = "editor"; @@ -319,22 +401,22 @@ namespace tut dp.rect = LLRect(0, 200, 400, 0); doc.compareView = LLUICtrlFactory::create(dp); window.floater->addChild(doc.compareView); - ensure("the source's", doc.undoText() == doc.editor); + UNSCOPED_INFO("the source's"); + CHECK(doc.undoText() == doc.editor); doc.view = Doc::View::Compare; - ensure("a comparison of texts of no tab's: its side's, which has none", doc.undoText() == doc.compareView->shown()); + UNSCOPED_INFO("a comparison of texts of no tab's: its side's, which has none"); + CHECK(doc.undoText() == doc.compareView->shown()); doc.compareTitles = Doc::CompareTitles{ "Saved", "Now" }; - ensure("one that follows the tab: the tab's", doc.undoText() == doc.editor); + UNSCOPED_INFO("one that follows the tab: the tab's"); + CHECK(doc.undoText() == doc.editor); } - template<> template<> - void alscriptstudiodoc_object::test<10>() + TEST_CASE_METHOD(alscriptstudiodoc_data, "ALScriptStudioDoc: a save's envelope carries an upload header only where one is asked for", "[ALScriptStudioDoc]") { - set_test_name("a save's envelope carries an upload header where one is asked for -- what the save hashes to, when and who, " - "between the target line and the code, which it moves down by its lines -- and none where not"); al_studio_test::StudioWindow window; if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } ALCodeEditor::Params p(LLUICtrlFactory::getDefaultParams()); p.name = "editor"; @@ -347,20 +429,32 @@ namespace tut const std::string expanded = "default { state_entry() { llOwnerSay((string)1); } }\n"; const ALScriptEnvelope plain = doc.envelopeFor(expanded, "Test 1.0"); - ensure("none unasked", plain.header.empty()); + UNSCOPED_INFO("none unasked"); + CHECK(plain.header.empty()); ALScriptHeaderOptions asked; asked.on = true; asked.creator = "Some One"; const ALScriptEnvelope with = doc.envelopeFor(expanded, "Test 1.0", asked); const std::optional said = ALUploadHeader::parse(with.header, false); - ensure("ours, read back: " + with.header, said.has_value() && said->ours); - ensure_equals("its hash", said->hash, ALUploadHeader::hashOf("mono", doc.editor->text(), expanded)); - ensure_equals("who", said->creator, std::string("Some One")); - ensure("no file: a tab has no link", said->file.empty()); + { + INFO("ours, read back: " + with.header); + REQUIRE(said.has_value()); + CHECK(said->ours); + } + CHECK(said->hash == ALUploadHeader::hashOf("mono", doc.editor->text(), expanded)); + CHECK(said->creator == std::string("Some One")); + UNSCOPED_INFO("no file: a tab has no link"); + CHECK(said->file.empty()); const int lines = static_cast(std::count(with.header.begin(), with.header.end(), '\n')); - ensure_equals("the code its lines down", with.codeLine(), plain.codeLine() + lines); + UNSCOPED_INFO("the code its lines down"); + CHECK(with.codeLine() == plain.codeLine() + lines); const std::optional back = ALScriptEnvelope::parse(with.wrap()); - ensure("parsed back with it", back.has_value() && back->header == with.header && back->expanded == expanded); + { + INFO("parsed back with it"); + REQUIRE(back.has_value()); + CHECK(back->header == with.header); + CHECK(back->expanded == expanded); + } } } diff --git a/indra/newview/tests/alscriptstudioexpandedcompare_test.cpp b/indra/newview/tests/alscriptstudioexpandedcompare_test.cpp index a59469f858..5c42c6bd93 100644 --- a/indra/newview/tests/alscriptstudioexpandedcompare_test.cpp +++ b/indra/newview/tests/alscriptstudioexpandedcompare_test.cpp @@ -31,7 +31,7 @@ #include "alscriptstudio_fixture.h" #include "alsourcemap.h" -#include "../test/lltut.h" +#include #include @@ -93,7 +93,7 @@ namespace } } -namespace tut +namespace { struct alscriptstudioexpandedcompare_data { @@ -148,14 +148,8 @@ namespace tut } }; - typedef test_group alscriptstudioexpandedcompare_group; - typedef alscriptstudioexpandedcompare_group::object alscriptstudioexpandedcompare_object; - tut::alscriptstudioexpandedcompare_group alscriptstudioexpandedcompare_test("alscriptstudioexpandedcompare"); - - template<> template<> - void alscriptstudioexpandedcompare_object::test<1>() + TEST_CASE_METHOD(alscriptstudioexpandedcompare_data, "alscriptstudioexpandedcompare: rangesOf pairs each output line with the source lines it came from", "[alscriptstudioexpandedcompare]") { - set_test_name("each output line beside the script's own lines it came from; lines standing for the same joined; an include's and nothing's in none"); ALSourceMap map; map.addFile("script", std::string()); map.addFile("include", "include.lsl"); @@ -169,89 +163,105 @@ namespace tut map.add(segment(4, 0, 8, 0, 3, 2, false)); map.add(segment(6, 0, 4, 0, 5, 0)); map.finish(); - ensure_equals("the ranges", said(ALScriptStudioExpandedCompare::rangesOf(map)), std::string("[0-0 1-1][1-2 2-2][3-3 3-4][5-5 6-6]")); - ensure("none for nothing", ALScriptStudioExpandedCompare::rangesOf(ALSourceMap()).empty()); + CHECK(said(ALScriptStudioExpandedCompare::rangesOf(map)) == std::string("[0-0 1-1][1-2 2-2][3-3 3-4][5-5 6-6]")); + CHECK(ALScriptStudioExpandedCompare::rangesOf(ALSourceMap()).empty()); } - template<> template<> - void alscriptstudioexpandedcompare_object::test<2>() + TEST_CASE_METHOD(alscriptstudioexpandedcompare_data, "alscriptstudioexpandedcompare: the source is lined up beside what a save sends and typing maps back", "[alscriptstudioexpandedcompare]") { - set_test_name("the source beside what a save sends, lined up by the map, the include's line alone; typing goes on in the source where the map says"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& door = tab("door"); - ensure("it can be compared", ALScriptStudioExpandedCompare::canCompare(door)); + CHECK(ALScriptStudioExpandedCompare::canCompare(door)); unit->compare(door); - ensure("shown at once, no run asked", studio.compares == 1 && studio.runs == 0); - ensure_equals("under the tab's name and the expansion's", studio.titles, "door | " + services().words("ComparePreprocessed")); - ensure_equals("lined up by line", said(studio.given), std::string("[0-0 1-1][1-1 2-2]")); + { + INFO("shown at once, no run asked"); + CHECK(studio.compares == 1); + CHECK(studio.runs == 0); + } + UNSCOPED_INFO("under the tab's name and the expansion's"); + CHECK(studio.titles == "door | " + services().words("ComparePreprocessed")); + UNSCOPED_INFO("lined up by line"); + CHECK(said(studio.given) == std::string("[0-0 1-1][1-1 2-2]")); ALDiffView& view = *door.compareView; - ensure("the source's first line beside the expansion's second", - view.left()->layout().lineTop(0) == view.right()->layout().lineTop(1)); - ensure_equals("the include's line put in, nothing else changed", view.changeCount(), 1); + UNSCOPED_INFO("the source's first line beside the expansion's second"); + CHECK(view.left()->layout().lineTop(0) == view.right()->layout().lineTop(1)); + UNSCOPED_INFO("the include's line put in, nothing else changed"); + CHECK(view.changeCount() == 1); view.right()->setFocus(true); view.right()->goTo(ALTextPos(2, 8)); view.right()->handleUnicodeChar('x', false); - ensure("typed where the line came from", studio.typedAt.size() == 1 && studio.typedAt[0] == ALTextPos(1, 8)); - ensure_equals("in the source", door.editor->wholeText(), std::string("integer a;\ninteger xb;")); + { + INFO("typed where the line came from"); + REQUIRE(studio.typedAt.size() == 1); + CHECK(studio.typedAt[0] == ALTextPos(1, 8)); + } + UNSCOPED_INFO("in the source"); + CHECK(door.editor->wholeText() == std::string("integer a;\ninteger xb;")); unit->compare(door); view.right()->setFocus(true); view.right()->goTo(ALTextPos(0, 3)); view.right()->handleUnicodeChar('y', false); - ensure("an include's line: nowhere", studio.typedAt.size() == 1); + UNSCOPED_INFO("an include's line: nowhere"); + CHECK(studio.typedAt.size() == 1); } - template<> template<> - void alscriptstudioexpandedcompare_object::test<3>() + TEST_CASE_METHOD(alscriptstudioexpandedcompare_data, "alscriptstudioexpandedcompare: a stale expansion is run again first and shown once current", "[alscriptstudioexpandedcompare]") { - set_test_name("an expansion older than the text made again first, and the comparison shown once one of the text as it is comes; a tab closed while it waits let go of"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& door = tab("door"); door.editor->goTo(ALTextPos(0, 0)); door.editor->insertText("// "); unit->compare(door); - ensure("a run asked, nothing shown", studio.runs == 1 && studio.compares == 0); + { + INFO("a run asked, nothing shown"); + CHECK(studio.runs == 1); + CHECK(studio.compares == 0); + } unit->expanded(door); - ensure("a run of the older text: still waiting", studio.compares == 0); + UNSCOPED_INFO("a run of the older text: still waiting"); + CHECK(studio.compares == 0); door.uploaded.version = door.editor->document().version(); unit->expanded(door); - ensure("shown", studio.compares == 1); + CHECK(studio.compares == 1); unit->expanded(door); - ensure("once", studio.compares == 1); + UNSCOPED_INFO("once"); + CHECK(studio.compares == 1); // A tab closed while it waited is let go of as the next is asked. Doc& gate = tab("gate"); gate.editor->insertText("// "); unit->compare(gate); - ensure("waiting", unit->waiting("gate")); + CHECK(unit->waiting("gate")); services().docs.erase(std::remove_if(services().docs.begin(), services().docs.end(), [](const std::unique_ptr& doc) { return doc->id == "gate"; }), services().docs.end()); unit->expanded(door); - ensure("closed: let go of", !unit->waiting("gate")); + UNSCOPED_INFO("closed: let go of"); + CHECK_FALSE(unit->waiting("gate")); Doc& lamp = tab("lamp"); lamp.expandedEditor = nullptr; - ensure("not one a save preprocesses", !ALScriptStudioExpandedCompare::canCompare(lamp)); + UNSCOPED_INFO("not one a save preprocesses"); + CHECK_FALSE(ALScriptStudioExpandedCompare::canCompare(lamp)); lamp.expandedEditor = editor("expanded_again", ""); lamp.loaded = false; - ensure("nor before it has loaded", !ALScriptStudioExpandedCompare::canCompare(lamp)); + UNSCOPED_INFO("nor before it has loaded"); + CHECK_FALSE(ALScriptStudioExpandedCompare::canCompare(lamp)); } - template<> template<> - void alscriptstudioexpandedcompare_object::test<4>() + TEST_CASE_METHOD(alscriptstudioexpandedcompare_data, "alscriptstudioexpandedcompare: a real preprocessor run lines each source line up with its output", "[alscriptstudioexpandedcompare]") { - set_test_name("over a real run: an include's lines alone on the right, every line of the script's own beside what it became, a macro's call beside what it made"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } const std::string source = "#include \"lib.lsl\"\n" "#define GREET(x) llOwnerSay(x)\n" @@ -274,7 +284,8 @@ namespace tut return ALPreprocessor::Found::Yes; }; const ALPreprocessor::Result result = ALPreprocessor::run(source, options); - ensure("expanded", result.text.find("llOwnerSay(\"hi\")") != std::string::npos); + UNSCOPED_INFO("expanded"); + REQUIRE(result.text.find("llOwnerSay(\"hi\")") != std::string::npos); Doc& door = tab("door"); door.editor->setText(source); @@ -295,61 +306,90 @@ namespace tut return -1; }; const auto beside = [&view](S32 left, S32 right) { return right >= 0 && view.left()->layout().lineTop(left) == view.right()->layout().lineTop(right); }; - ensure("the include's lines in the output", where("gLib") >= 0); - ensure("default beside default", beside(2, where("default"))); - ensure("state_entry beside state_entry", beside(4, where("state_entry"))); - ensure("the macro's call beside what it made", beside(6, where("llOwnerSay"))); - ensure("the last brace beside the last brace", beside(8, static_cast(out.size()) - (out.back().empty() ? 2 : 1))); + UNSCOPED_INFO("the include's lines in the output"); + CHECK(where("gLib") >= 0); + UNSCOPED_INFO("default beside default"); + CHECK(beside(2, where("default"))); + UNSCOPED_INFO("state_entry beside state_entry"); + CHECK(beside(4, where("state_entry"))); + UNSCOPED_INFO("the macro's call beside what it made"); + CHECK(beside(6, where("llOwnerSay"))); + UNSCOPED_INFO("the last brace beside the last brace"); + CHECK(beside(8, static_cast(out.size()) - (out.back().empty() ? 2 : 1))); // What the map alone says: the call's line stands for what it made. const S32 call = view.model().rangeAt(ALDiffModel::Column::Left, 6); - ensure("the call's line in a range", call >= 0); - ensure_equals("whose output is what it made", view.model().ranges()[static_cast(call)].rightFirst, where("llOwnerSay")); - ensure("the directives in none", view.model().rangeAt(ALDiffModel::Column::Left, 0) < 0 && view.model().rangeAt(ALDiffModel::Column::Left, 1) < 0); + UNSCOPED_INFO("the call's line in a range"); + REQUIRE(call >= 0); + UNSCOPED_INFO("whose output is what it made"); + CHECK(view.model().ranges()[static_cast(call)].rightFirst == where("llOwnerSay")); + { + INFO("the directives in none"); + CHECK(view.model().rangeAt(ALDiffModel::Column::Left, 0) < 0); + CHECK(view.model().rangeAt(ALDiffModel::Column::Left, 1) < 0); + } } - template<> template<> - void alscriptstudioexpandedcompare_object::test<5>() + TEST_CASE_METHOD(alscriptstudioexpandedcompare_data, "alscriptstudioexpandedcompare: a stale run's answer asks again and a wait is dropped when the tab moves on", "[alscriptstudioexpandedcompare]") { - set_test_name("a run of an older text answering, its own dropped while that one was on its way, asks again; said while it waits; let go of once the tab shows something else"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& door = tab("door"); door.editor->goTo(ALTextPos(0, 0)); door.editor->insertText("// "); unit->compare(door); - ensure("a run asked", studio.runs == 1); - ensure_equals("said while it waits", services().statuses.back(), services().words("Preprocessing", { { "[NAME]", "door" } })); + CHECK(studio.runs == 1); + UNSCOPED_INFO("said while it waits"); + CHECK(services().statuses.back() == services().words("Preprocessing", { { "[NAME]", "door" } })); // The window was busy: the run that answers is the one before. unit->expanded(door); - ensure("asked again rather than left waiting", studio.runs == 2 && studio.compares == 0 && unit->waiting("door")); + { + INFO("asked again rather than left waiting"); + CHECK(studio.runs == 2); + CHECK(studio.compares == 0); + CHECK(unit->waiting("door")); + } door.uploaded.version = door.editor->document().version(); unit->expanded(door); - ensure("shown once the run of the text now answers", studio.compares == 1 && !unit->waiting("door")); + { + INFO("shown once the run of the text now answers"); + CHECK(studio.compares == 1); + CHECK_FALSE(unit->waiting("door")); + } // Another comparison started while it waits: let go of, not put // over it. door.view = Doc::View::Source; door.editor->insertText("// "); unit->compare(door); - ensure("waiting", unit->waiting("door")); + CHECK(unit->waiting("door")); studio.compareRanged(door, "other", door.editor->wholeText(), "other", "door", {}); door.view = Doc::View::Compare; door.uploaded.version = door.editor->document().version(); const S32 runs = studio.runs; unit->expanded(door); - ensure("let go of", !unit->waiting("door")); - ensure("the other comparison left as it is", studio.compares == 2 && studio.runs == runs && studio.titles == "other | door"); + UNSCOPED_INFO("let go of"); + CHECK_FALSE(unit->waiting("door")); + { + INFO("the other comparison left as it is"); + CHECK(studio.compares == 2); + CHECK(studio.runs == runs); + CHECK(studio.titles == "other | door"); + } // Asked from a comparison, which is then closed: let go of. door.editor->insertText("// "); unit->compare(door); - ensure("waiting again", unit->waiting("door")); + CHECK(unit->waiting("door")); door.view = Doc::View::Source; door.uploaded.version = door.editor->document().version(); unit->expanded(door); - ensure("closed: let go of", !unit->waiting("door") && studio.compares == 2); + { + INFO("closed: let go of"); + CHECK_FALSE(unit->waiting("door")); + CHECK(studio.compares == 2); + } // Still in the comparison it was asked from, which follows the // tab: shown over it. @@ -359,6 +399,7 @@ namespace tut door.compareView->setRightText(door.editor->wholeText()); door.uploaded.version = door.editor->document().version(); unit->expanded(door); - ensure("shown", studio.compares == 3 && !unit->waiting("door")); + CHECK(studio.compares == 3); + CHECK_FALSE(unit->waiting("door")); } } diff --git a/indra/newview/tests/alscriptstudiofiles_test.cpp b/indra/newview/tests/alscriptstudiofiles_test.cpp index 4d27ef2112..d8b9eafdc0 100644 --- a/indra/newview/tests/alscriptstudiofiles_test.cpp +++ b/indra/newview/tests/alscriptstudiofiles_test.cpp @@ -33,7 +33,7 @@ #include "fsyspath.h" -#include "../test/lltut.h" +#include #include #include @@ -111,7 +111,7 @@ namespace }; } -namespace tut +namespace { struct alscriptstudiofiles_data { @@ -136,7 +136,7 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } const std::string name = "alscriptstudiofiles_" + LLUUID::generateNewID().asString(); const fsyspath made = std::filesystem::temp_directory_path() / fsyspath(name); @@ -189,67 +189,96 @@ namespace tut const al_studio_test::FakeServices::Said& said() const { return services.reports.back(); } }; - typedef test_group alscriptstudiofiles_group; - typedef alscriptstudiofiles_group::object alscriptstudiofiles_object; - alscriptstudiofiles_group alscriptstudiofiles_instance("alscriptstudiofiles"); - - template<> template<> - void alscriptstudiofiles_object::test<1>() + TEST_CASE_METHOD(alscriptstudiofiles_data, "alscriptstudiofiles: what a file's name says it holds", "[alscriptstudiofiles]") { - set_test_name("what a file's name says it holds"); typedef ALScriptStudioFiles F; - ensure("LSL", F::languageOf("/a/b.lsl", false).script && !F::languageOf("/a/b.lsl", true).lua); - ensure("SLua by .luau and .lua, whatever the case", F::languageOf("/a/b.LUAU", false).lua && F::languageOf("/a/b.lua", false).lua); - ensure("text", !F::languageOf("/a/b.txt", false).script && F::languageOf("/a/b.txt", false).said); - ensure("no extension: what it was asked for as", F::languageOf("/a/b", true).script && F::languageOf("/a/b", true).lua && - !F::languageOf("/a/b", true).said); + { + INFO("LSL"); + CHECK(F::languageOf("/a/b.lsl", false).script); + CHECK_FALSE(F::languageOf("/a/b.lsl", true).lua); + } + { + INFO("SLua by .luau and .lua, whatever the case"); + CHECK(F::languageOf("/a/b.LUAU", false).lua); + CHECK(F::languageOf("/a/b.lua", false).lua); + } + { + INFO("text"); + CHECK_FALSE(F::languageOf("/a/b.txt", false).script); + CHECK(F::languageOf("/a/b.txt", false).said); + } + { + INFO("no extension: what it was asked for as"); + CHECK(F::languageOf("/a/b", true).script); + CHECK(F::languageOf("/a/b", true).lua); + CHECK_FALSE(F::languageOf("/a/b", true).said); + } } - template<> template<> - void alscriptstudiofiles_object::test<2>() + TEST_CASE_METHOD(alscriptstudiofiles_data, "alscriptstudiofiles: the recent lists keep ten newest first each once and survive the state", "[alscriptstudiofiles]") { - set_test_name("the recent lists: newest first, each once, ten at most; a file tab not a script; cleared; kept with the state"); ALScriptStudioFiles& unit = make(); for (int i = 0; i < 12; ++i) { unit.noteFile("/f" + std::to_string(i)); } unit.noteFile("/f5"); - ensure_equals("ten", unit.recentFiles().size(), size_t(10)); + REQUIRE(unit.recentFiles().size() == size_t(10)); const Names& files = unit.recentFiles(); - ensure("newest first, the one noted again moved up", - files[0] == "/f5" && files[1] == "/f11" && std::count(files.begin(), files.end(), "/f5") == 1); + { + INFO("newest first, the one noted again moved up"); + CHECK(files[0] == "/f5"); + CHECK(files[1] == "/f11"); + CHECK(std::count(files.begin(), files.end(), "/f5") == 1); + } Doc& a = scriptTab("a", "x"); Doc& b = scriptTab("b", "y"); unit.noteScript(a); unit.noteScript(b); unit.noteScript(a); - ensure("scripts each once, newest first", unit.recentScripts().size() == 2 && unit.recentScripts()[0].ref == a.ref); + { + INFO("scripts each once, newest first"); + REQUIRE(unit.recentScripts().size() == 2); + CHECK(unit.recentScripts()[0].ref == a.ref); + } Doc& f = fileTab("f.lsl", "z"); unit.noteScript(f); - ensure("a file's tab is no script", unit.recentScripts().size() == 2); - ensure_equals("the window told each time", studio.recentTold, 16); + UNSCOPED_INFO("a file's tab is no script"); + CHECK(unit.recentScripts().size() == 2); + UNSCOPED_INFO("the window told each time"); + CHECK(studio.recentTold == 16); LLSD state; unit.writeState(state); ALScriptStudioFiles again(services, studio, studio, studio, studio); again.readState(state); - ensure("read back as written", again.recentFiles() == unit.recentFiles() && again.recentScripts().size() == 2 && - again.recentScripts()[1].ref == b.ref && again.recentScripts()[1].name == "b"); + { + INFO("read back as written"); + CHECK(again.recentFiles() == unit.recentFiles()); + REQUIRE(again.recentScripts().size() == 2); + CHECK(again.recentScripts()[1].ref == b.ref); + CHECK(again.recentScripts()[1].name == "b"); + } unit.clearRecent(); - ensure("cleared", unit.recentFiles().empty() && unit.recentScripts().empty() && studio.recentTold == 17); + { + INFO("cleared"); + CHECK(unit.recentFiles().empty()); + CHECK(unit.recentScripts().empty()); + CHECK(studio.recentTold == 17); + } LLSD odd; odd["recent_files"] = LLSD().with(0, "/x").with(1, "").with(2, "/x").with(3, "/y"); odd["recent_scripts"] = LLSD().with(0, LLSD().with("object", LLUUID::null).with("item", LLUUID::null).with("name", "none")); again.readState(odd); - ensure("read back: each once, none empty, no script without what holds it", - again.recentFiles() == Names{ "/x", "/y" } && again.recentScripts().empty()); + { + INFO("read back: each once, none empty, no script without what holds it"); + CHECK(again.recentFiles() == Names{ "/x", "/y" }); + CHECK(again.recentScripts().empty()); + } } - template<> template<> - void alscriptstudiofiles_object::test<3>() + TEST_CASE_METHOD(alscriptstudiofiles_data, "alscriptstudiofiles: Open Recent lists scripts then files and a folder where names clash", "[alscriptstudiofiles]") { - set_test_name("Open Recent filled: scripts then files under their headings, a folder where two share a name; nothing to say so"); ALScriptStudioFiles& unit = make(); LLMenuGL::Params p; p.name = "open_recent"; @@ -258,175 +287,267 @@ namespace tut // menus, which there are none of here. studio.menu->setVisible(false); unit.fillMenu(); - ensure("nothing", studio.menu->findChild("no_recent") != nullptr); + CHECK(studio.menu->findChild("no_recent") != nullptr); unit.noteFile("/one/same.lsl"); unit.noteFile("/two/same.lsl"); unit.noteFile("/two/other.lsl"); - ensure("files alone: no heading", studio.menu->findChild("recent_heading_RecentFiles") == nullptr && - studio.menu->findChild("recent_/two/other.lsl") != nullptr); + { + INFO("files alone: no heading"); + CHECK(studio.menu->findChild("recent_heading_RecentFiles") == nullptr); + CHECK(studio.menu->findChild("recent_/two/other.lsl") != nullptr); + } Doc& a = scriptTab("a", "x"); unit.noteScript(a); - ensure("both: headings", studio.menu->findChild("recent_heading_RecentScripts") != nullptr && - studio.menu->findChild("recent_heading_RecentFiles") != nullptr); + { + INFO("both: headings"); + CHECK(studio.menu->findChild("recent_heading_RecentScripts") != nullptr); + CHECK(studio.menu->findChild("recent_heading_RecentFiles") != nullptr); + } LLMenuItemCallGL* same = studio.menu->findChild("recent_/one/same.lsl"); - ensure("a folder after a name two share", same && same->getLabel().find("(/one)") != std::string::npos); + { + INFO("a folder after a name two share"); + REQUIRE(same); + CHECK(same->getLabel().find("(/one)") != std::string::npos); + } LLMenuItemCallGL* other = studio.menu->findChild("recent_/two/other.lsl"); - ensure("one alone, by its name", other && other->getLabel() == "other.lsl"); + { + INFO("one alone, by its name"); + REQUIRE(other); + CHECK(other->getLabel() == "other.lsl"); + } other->onCommit(); - ensure("picked: opened", studio.opened == Names{ "/two/other.lsl" }); + UNSCOPED_INFO("picked: opened"); + CHECK(studio.opened == Names{ "/two/other.lsl" }); LLMenuItemCallGL* script = studio.menu->findChild("recent_" + a.ref.id()); - ensure("a script by its name", script && script->getLabel() == "a"); + { + INFO("a script by its name"); + REQUIRE(script); + CHECK(script->getLabel() == "a"); + } script->onCommit(); - ensure("picked: opened as a script", !services.opened.empty() && services.opened.back().ref == a.ref); + { + INFO("picked: opened as a script"); + REQUIRE_FALSE(services.opened.empty()); + CHECK(services.opened.back().ref == a.ref); + } studio.menu->findChild("clear_recent")->onCommit(); - ensure("the clear item clears", unit.recentFiles().empty() && studio.menu->findChild("no_recent") != nullptr); + { + INFO("the clear item clears"); + CHECK(unit.recentFiles().empty()); + CHECK(studio.menu->findChild("no_recent") != nullptr); + } studio.menu->die(); studio.menu = nullptr; } - template<> template<> - void alscriptstudiofiles_object::test<4>() + TEST_CASE_METHOD(alscriptstudiofiles_data, "alscriptstudiofiles: writing a tab's file settles it and a failed write stops the save", "[alscriptstudiofiles]") { - set_test_name("a tab's file written: said, settled and told of; one that will not go said, and the save stopped"); ALScriptStudioFiles& unit = make(); Doc& f = fileTab("f.lsl", "old"); unit.watch(f); f.editor->setText("new"); unit.write(f); - ensure_equals("written", contents(f.file), std::string("new")); - ensure("its own write is no change to the watch", !f.watch->check() && !f.watch->check()); - ensure("said, settled, told of", !said().failure && studio.settled == Names{ f.id } && studio.written == Names{ f.file }); + CHECK(contents(f.file) == std::string("new")); + { + INFO("its own write is no change to the watch"); + CHECK_FALSE(f.watch->check()); + CHECK_FALSE(f.watch->check()); + } + { + INFO("said, settled, told of"); + CHECK_FALSE(said().failure); + CHECK(studio.settled == Names{ f.id }); + CHECK(studio.written == Names{ f.file }); + } Doc& g = fileTab("g.lsl", "x"); g.file = in("no/such/folder/g.lsl"); unit.write(g); - ensure("will not go: said and stopped, not settled", - said().failure && studio.stops == Names{ g.id } && studio.settled.size() == 1); + { + INFO("will not go: said and stopped, not settled"); + CHECK(said().failure); + CHECK(studio.stops == Names{ g.id }); + CHECK(studio.settled.size() == 1); + } } - template<> template<> - void alscriptstudiofiles_object::test<5>() + TEST_CASE_METHOD(alscriptstudiofiles_data, "alscriptstudiofiles: an outside change is taken into a clean tab and asked about otherwise", "[alscriptstudiofiles]") { - set_test_name("a file changed outside is taken where the tab is clean; asked about once where it is not, and read again if so answered"); ALScriptStudioFiles& unit = make(); Doc& f = fileTab("f.lsl", "one"); unit.watch(f); - ensure("watched", f.watch && f.watch->path() == f.file); + { + INFO("watched"); + REQUIRE(f.watch); + CHECK(f.watch->path() == f.file); + } write(f.file, "two"); unit.changedOutside(f.id, f.file); - ensure("clean: taken, settled, said", studio.taken == Names{ "two" } && studio.settled == Names{ f.id } && !said().failure); + { + INFO("clean: taken, settled, said"); + CHECK(studio.taken == Names{ "two" }); + CHECK(studio.settled == Names{ f.id }); + CHECK_FALSE(said().failure); + } // Read in, not written: what it masters is the masters' watch's to // send, not the tab's. - ensure("not taken for a write of the studio's", studio.written.empty()); + UNSCOPED_INFO("not taken for a write of the studio's"); + CHECK(studio.written.empty()); unit.changedOutside(f.id, f.file); - ensure("the same again: nothing", studio.taken.size() == 1); + UNSCOPED_INFO("the same again: nothing"); + CHECK(studio.taken.size() == 1); f.editor->setCaret(f.editor->document().end()); f.editor->insertText(" typed"); write(f.file, "three"); unit.changedOutside(f.id, f.file); - ensure("not clean: said and asked, not taken", said().failure && studio.asked == Names{ f.id } && studio.taken.size() == 1); + { + INFO("not clean: said and asked, not taken"); + CHECK(said().failure); + CHECK(studio.asked == Names{ f.id }); + CHECK(studio.taken.size() == 1); + } unit.changedOutside(f.id, f.file); - ensure("asked once while the question is up", studio.asked.size() == 1); + UNSCOPED_INFO("asked once while the question is up"); + CHECK(studio.asked.size() == 1); studio.answer(false); - ensure("kept: nothing read", studio.reverted.empty() && !f.askingReload); + { + INFO("kept: nothing read"); + CHECK(studio.reverted.empty()); + CHECK_FALSE(f.askingReload); + } unit.changedOutside(f.id, f.file); studio.answer(true); - ensure("read again", studio.reverted == Names{ f.id }); + UNSCOPED_INFO("read again"); + CHECK(studio.reverted == Names{ f.id }); unit.changedOutside("nobody", f.file); const S32 before = studio.reachChanges; std::filesystem::remove(fsyspath(f.file)); unit.changedOutside(f.id, f.file); - ensure("gone or unknown: nothing", studio.asked.size() == 2); - ensure("but what is in reach looked at again", studio.reachChanges == before + 1); + UNSCOPED_INFO("gone or unknown: nothing"); + CHECK(studio.asked.size() == 2); + UNSCOPED_INFO("but what is in reach looked at again"); + CHECK(studio.reachChanges == before + 1); } - template<> template<> - void alscriptstudiofiles_object::test<6>() + TEST_CASE_METHOD(alscriptstudiofiles_data, "alscriptstudiofiles: Open picks several and Load and Insert one for the tab asked from", "[alscriptstudiofiles]") { - set_test_name("the pickers: Open several, Load and Insert one for the tab asked from, whatever is in front when the answer comes"); ALScriptStudioFiles& unit = make(); unit.openFromDisk(); - ensure("several", studio.pickedSeveral); + CHECK(studio.pickedSeveral); studio.toOpen({ "/a.lsl", "/b.luau" }); - ensure("each opened", studio.opened == Names{ "/a.lsl", "/b.luau" }); + UNSCOPED_INFO("each opened"); + CHECK(studio.opened == Names{ "/a.lsl", "/b.luau" }); Doc& a = scriptTab("a", "first"); write(in("load.txt"), "loaded"); unit.load(false); - ensure("one", !studio.pickedSeveral); + CHECK_FALSE(studio.pickedSeveral); Doc& b = scriptTab("b", "other"); services.front = 1; studio.toOpen({ in("load.txt") }); - ensure("the tab asked from, brought forward, its text replaced", a.editor->text() == "loaded" && b.editor->text() == "other" && - studio.activated == Names{ "a" }); + { + INFO("the tab asked from, brought forward, its text replaced"); + CHECK(a.editor->text() == "loaded"); + CHECK(b.editor->text() == "other"); + CHECK(studio.activated == Names{ "a" }); + } services.front = 1; b.editor->setCaret(ALTextPos(0, 2)); unit.load(true); studio.toOpen({ in("load.txt") }); - ensure_equals("inserted at the caret", b.editor->text(), std::string("otloadedher")); + UNSCOPED_INFO("inserted at the caret"); + CHECK(b.editor->text() == std::string("otloadedher")); unit.chosenToLoad("b", { in("nothing.txt") }, false); - ensure("unreadable: said", !services.statuses.empty() && b.editor->text() == "otloadedher"); + { + INFO("unreadable: said"); + CHECK_FALSE(services.statuses.empty()); + CHECK(b.editor->text() == "otloadedher"); + } b.modifiable = false; unit.chosenToLoad("b", { in("load.txt") }, false); - ensure("unchangeable: left", b.editor->text() == "otloadedher"); + UNSCOPED_INFO("unchangeable: left"); + CHECK(b.editor->text() == "otloadedher"); } - template<> template<> - void alscriptstudiofiles_object::test<7>() + TEST_CASE_METHOD(alscriptstudiofiles_data, "alscriptstudiofiles: Save to File writes a copy and Save As makes the tab the new file", "[alscriptstudiofiles]") { - set_test_name("Save to File writes a copy; Save As makes the tab the new file, watched there and noted; the same file is a save"); ALScriptStudioFiles& unit = make(); Doc& s = scriptTab("s", "script text"); services.front = 0; unit.saveCopy(); - ensure_equals("named after the tab", studio.savedAs, std::string("s")); + UNSCOPED_INFO("named after the tab"); + CHECK(studio.savedAs == std::string("s")); studio.toSave({ in("copy.lsl") }); - ensure("a copy written, said", contents(in("copy.lsl")) == "script text" && !said().failure); + { + INFO("a copy written, said"); + CHECK(contents(in("copy.lsl")) == "script text"); + CHECK_FALSE(said().failure); + } Doc& f = fileTab("f.lsl", "file text"); services.front = 1; unit.watch(f); unit.saveAs(); studio.toSave({ in("g.luau") }); - ensure_equals("written there", contents(in("g.luau")), std::string("file text")); - ensure("the tab that file now", studio.became == Names{ in("g.luau") } && f.file == in("g.luau")); - ensure("watched there", f.watch && f.watch->path() == in("g.luau")); - ensure("noted, settled, checked", unit.recentFiles().front() == in("g.luau") && studio.settled.back() == f.id && - studio.checked.back() == f.id + " now"); - ensure("told of as written", studio.written == Names{ in("g.luau") }); + UNSCOPED_INFO("written there"); + CHECK(contents(in("g.luau")) == std::string("file text")); + { + INFO("the tab that file now"); + CHECK(studio.became == Names{ in("g.luau") }); + CHECK(f.file == in("g.luau")); + } + { + INFO("watched there"); + REQUIRE(f.watch); + CHECK(f.watch->path() == in("g.luau")); + } + { + INFO("noted, settled, checked"); + CHECK(unit.recentFiles().front() == in("g.luau")); + CHECK(studio.settled.back() == f.id); + CHECK(studio.checked.back() == f.id + " now"); + } + UNSCOPED_INFO("told of as written"); + CHECK(studio.written == Names{ in("g.luau") }); f.editor->setText("changed"); unit.chosenToSaveAs(f.id, { f.file }); - ensure("its own file again: a save", contents(f.file) == "changed" && studio.became.size() == 1); + { + INFO("its own file again: a save"); + CHECK(contents(f.file) == "changed"); + CHECK(studio.became.size() == 1); + } Doc& other = fileTab("other.lsl", "o"); unit.chosenToSaveAs(f.id, { other.file }); - ensure("a file another tab has: said, and nothing written", !services.statuses.empty() && - services.statuses.back().find("FileOpenElsewhere") == 0 && - contents(other.file) == "o" && studio.became.size() == 1); + { + INFO("a file another tab has: said, and nothing written"); + REQUIRE_FALSE(services.statuses.empty()); + CHECK(services.statuses.back().find("FileOpenElsewhere") == 0); + CHECK(contents(other.file) == "o"); + CHECK(studio.became.size() == 1); + } } - template<> template<> - void alscriptstudiofiles_object::test<8>() + TEST_CASE_METHOD(alscriptstudiofiles_data, "alscriptstudiofiles: a file with a non-ASCII path is written and read back with LF endings", "[alscriptstudiofiles]") { - set_test_name("a file whose name and folder go past ASCII is written and read back, its line endings as an editor keeps them"); make(); // A user's folder and a script's name as a German speaker might // have them: on Windows, read in the ANSI code page, no such file. const fsyspath folder_path = fsyspath(in("J\xC3\xBCrgen")); std::filesystem::create_directories(folder_path); const std::string path = fsyspath(folder_path / fsyspath("\xC3\x9C" "berpr\xC3\xBC" "fung.lsl")).string(); - ensure("written", ALFileWrite::temp(path, "default\r\n{\r\n}\r\n")); + CHECK(ALFileWrite::temp(path, "default\r\n{\r\n}\r\n")); std::string text; - ensure("read back", ALScriptFileIO::readWholeFile(path, text)); - ensure_equals("as an editor keeps it", text, std::string("default\n{\n}\n")); - ensure("and where it was asked to be", std::filesystem::exists(fsyspath(path))); + REQUIRE(ALScriptFileIO::readWholeFile(path, text)); + UNSCOPED_INFO("as an editor keeps it"); + CHECK(text == std::string("default\n{\n}\n")); + UNSCOPED_INFO("and where it was asked to be"); + CHECK(std::filesystem::exists(fsyspath(path))); } - template<> template<> - void alscriptstudiofiles_object::test<9>() + TEST_CASE_METHOD(alscriptstudiofiles_data, "alscriptstudiofiles: pruneRecent drops scripts said gone and files no longer on disk", "[alscriptstudiofiles]") { - set_test_name("what was opened lately and cannot be had any more is let go of: a script said gone, a file no longer on disk"); ALScriptStudioFiles& unit = make(); - ensure("written", ALFileWrite::temp(in("here.lsl"), "x")); + CHECK(ALFileWrite::temp(in("here.lsl"), "x")); unit.noteFile(in("here.lsl")); unit.noteFile(in("gone.lsl")); Doc& a = scriptTab("a", "x"); @@ -434,33 +555,49 @@ namespace tut unit.noteScript(a); unit.noteScript(b); const S32 told = studio.recentTold; - ensure("the file no longer there let go of", unit.pruneRecent([](const ALScriptRef&) { return false; }) && - unit.recentFiles() == Names{ in("here.lsl") } && unit.recentScripts().size() == 2 && - studio.recentTold == told + 1); - ensure("nothing more gone, nothing done", !unit.pruneRecent([](const ALScriptRef&) { return false; }) && studio.recentTold == told + 1); + { + INFO("the file no longer there let go of"); + CHECK(unit.pruneRecent([](const ALScriptRef&) { return false; })); + CHECK(unit.recentFiles() == Names{ in("here.lsl") }); + CHECK(unit.recentScripts().size() == 2); + CHECK(studio.recentTold == told + 1); + } + { + INFO("nothing more gone, nothing done"); + CHECK_FALSE(unit.pruneRecent([](const ALScriptRef&) { return false; })); + CHECK(studio.recentTold == told + 1); + } const ALScriptRef lost = b.ref; - ensure("pruned", unit.pruneRecent([lost](const ALScriptRef& ref) { return ref == lost; })); - ensure("the gone script let go of", unit.recentScripts().size() == 1 && unit.recentScripts()[0].ref == a.ref); - ensure("the file still there kept", unit.recentFiles() == Names{ in("here.lsl") }); + CHECK(unit.pruneRecent([lost](const ALScriptRef& ref) { return ref == lost; })); + { + INFO("the gone script let go of"); + REQUIRE(unit.recentScripts().size() == 1); + CHECK(unit.recentScripts()[0].ref == a.ref); + } + UNSCOPED_INFO("the file still there kept"); + CHECK(unit.recentFiles() == Names{ in("here.lsl") }); } - template<> template<> - void alscriptstudiofiles_object::test<10>() + TEST_CASE_METHOD(alscriptstudiofiles_data, "alscriptstudiofiles: textSyntaxOf picks a grammar by extension in any case or else text", "[alscriptstudiofiles]") { - set_test_name("a file that is no script is coloured by the grammar whose file names its extension, whatever its case, and as text where none does"); // The grammars are the source tree's, which the UI is pointed at. if (!ll_test::HeadlessUI::get().ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } typedef ALScriptStudioFiles F; - ensure_equals("XML", F::textSyntaxOf("/a/s.XML"), std::string("xml")); - ensure_equals("a skin's file", F::textSyntaxOf("/a/s.xui"), std::string("xml")); - ensure_equals("JSON", F::textSyntaxOf("/a/s.json"), std::string("json")); - ensure_equals("a notecard of settings", F::textSyntaxOf("/a/door.ini"), std::string("config")); - ensure_equals("and another way of naming one", F::textSyntaxOf("/a/door.CFG"), std::string("config")); - ensure_equals("text", F::textSyntaxOf("/a/s.txt"), std::string("text")); - ensure_equals("a name no grammar knows", F::textSyntaxOf("/a/s.dat"), std::string("text")); - ensure_equals("and none", F::textSyntaxOf("/a/s"), std::string("text")); + CHECK(F::textSyntaxOf("/a/s.XML") == std::string("xml")); + UNSCOPED_INFO("a skin's file"); + CHECK(F::textSyntaxOf("/a/s.xui") == std::string("xml")); + CHECK(F::textSyntaxOf("/a/s.json") == std::string("json")); + UNSCOPED_INFO("a notecard of settings"); + CHECK(F::textSyntaxOf("/a/door.ini") == std::string("config")); + UNSCOPED_INFO("and another way of naming one"); + CHECK(F::textSyntaxOf("/a/door.CFG") == std::string("config")); + CHECK(F::textSyntaxOf("/a/s.txt") == std::string("text")); + UNSCOPED_INFO("a name no grammar knows"); + CHECK(F::textSyntaxOf("/a/s.dat") == std::string("text")); + UNSCOPED_INFO("and none"); + CHECK(F::textSyntaxOf("/a/s") == std::string("text")); } } diff --git a/indra/newview/tests/alscriptstudiohistory_test.cpp b/indra/newview/tests/alscriptstudiohistory_test.cpp index 49578d2de0..43586af280 100644 --- a/indra/newview/tests/alscriptstudiohistory_test.cpp +++ b/indra/newview/tests/alscriptstudiohistory_test.cpp @@ -34,7 +34,7 @@ #include "alscriptstudio_fixture.h" #include "fsyspath.h" -#include "../test/lltut.h" +#include #include @@ -93,7 +93,7 @@ namespace }; } -namespace tut +namespace { struct alscriptstudiohistory_data { @@ -140,106 +140,124 @@ namespace tut } // A save of a tab's item, so long ago. - void saved(const Doc& doc, const std::string& text, F64 ago, const LLUUID& asset = LLUUID::generateNewID()) + void require_saved(const Doc& doc, const std::string& text, F64 ago, const LLUUID& asset = LLUUID::generateNewID()) { ALSavedText one; one.key = ALScriptStudioHistory::keyOf(doc); one.text = text; one.when = LLDate(LLDate::now().secondsSinceEpoch() - ago); one.asset = asset; - ensure("kept", history->keep(one)); + INFO("kept in the history"); + REQUIRE(history->keep(one)); } }; - typedef test_group alscriptstudiohistory_group; - typedef alscriptstudiohistory_group::object alscriptstudiohistory_object; - tut::alscriptstudiohistory_group alscriptstudiohistory_test("alscriptstudiohistory"); - - template<> template<> - void alscriptstudiohistory_object::test<1>() + TEST_CASE_METHOD(alscriptstudiohistory_data, "alscriptstudiohistory: keyOf keys saves by item and object and gives a file no key", "[alscriptstudiohistory]") { - set_test_name("an item's saves are kept by the item, in its object or not; a file's are not kept here"); Doc doc; doc.ref = ALScriptRef(LLUUID::null, LLUUID::generateNewID()); - ensure_equals("an inventory item's", ALScriptStudioHistory::keyOf(doc), ALRecoveryStore::keyOf(LLUUID::null, doc.ref.item, std::string())); + UNSCOPED_INFO("an inventory item's"); + CHECK(ALScriptStudioHistory::keyOf(doc) == ALRecoveryStore::keyOf(LLUUID::null, doc.ref.item, std::string())); doc.ref = ALScriptRef(LLUUID::generateNewID(), LLUUID::generateNewID()); - ensure_equals("an object's", ALScriptStudioHistory::keyOf(doc), ALRecoveryStore::keyOf(doc.ref.object, doc.ref.item, std::string())); + UNSCOPED_INFO("an object's"); + CHECK(ALScriptStudioHistory::keyOf(doc) == ALRecoveryStore::keyOf(doc.ref.object, doc.ref.item, std::string())); doc.file = "/somewhere/door.lsl"; - ensure("none for a file", ALScriptStudioHistory::keyOf(doc).empty()); + UNSCOPED_INFO("none for a file"); + REQUIRE(ALScriptStudioHistory::keyOf(doc).empty()); } - template<> template<> - void alscriptstudiohistory_object::test<2>() + TEST_CASE_METHOD(alscriptstudiohistory_data, "alscriptstudiohistory: a tab's saves list newest first with their sizes or say there are none", "[alscriptstudiohistory]") { - set_test_name("a tab's saves listed newest first: when, how long, against the one before, and which is saved now; none said so"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door", "now"); unit.show(doc); - ensure("nothing to list", studio.candidates.empty() && !services().statuses.empty()); + { + INFO("nothing to list"); + REQUIRE(studio.candidates.empty()); + REQUIRE_FALSE(services().statuses.empty()); + } LLStringUtil::format_map_t args; args["[NAME]"] = "door"; - ensure_equals("said so", services().statuses.back(), said("HistoryNone", args)); + UNSCOPED_INFO("said so"); + CHECK(services().statuses.back() == said("HistoryNone", args)); const LLUUID current = LLUUID::generateNewID(); - saved(doc, "12345", 300.0); - saved(doc, "1234567", 200.0); - saved(doc, "123", 100.0, current); + require_saved(doc, "12345", 300.0); + require_saved(doc, "1234567", 200.0); + require_saved(doc, "123", 100.0, current); doc.assetId = current; unit.show(doc); - ensure_equals("three", studio.candidates.size(), 3U); - ensure_equals("titled", studio.picked, said("HistoryTitle", args)); + REQUIRE(studio.candidates.size() == 3U); + CHECK(studio.picked == said("HistoryTitle", args)); const std::vector listed = history->list(ALScriptStudioHistory::keyOf(doc)); - ensure_equals("labelled by when", studio.candidates[0].label, ALRecoveryEntry::sayWhen(listed[0].when)); + CHECK(studio.candidates[0].label == ALRecoveryEntry::sayWhen(listed[0].when)); const std::string& newest = studio.candidates[0].detail; - ensure("its length" + newest, newest.find(services().counted("HistoryBytes", 3)) != std::string::npos); - ensure("shorter than the one before" + newest, newest.find(services().counted("HistoryShorter", 4)) != std::string::npos); - ensure("saved now" + newest, newest.find(said("HistoryCurrent")) != std::string::npos); + { + CAPTURE(newest); + REQUIRE(newest.find(services().counted("HistoryBytes", 3)) != std::string::npos); + REQUIRE(newest.find(services().counted("HistoryShorter", 4)) != std::string::npos); + REQUIRE(newest.find(said("HistoryCurrent")) != std::string::npos); + } const std::string& middle = studio.candidates[1].detail; - ensure("longer" + middle, middle.find(services().counted("HistoryLonger", 2)) != std::string::npos); - ensure("not saved now" + middle, middle.find(said("HistoryCurrent")) == std::string::npos); - ensure("the first kept says nothing of one before", studio.candidates[2].detail.find(said("HistorySameLength")) == std::string::npos && - studio.candidates[2].detail.find("longer") == std::string::npos); + { + CAPTURE(middle); + REQUIRE(middle.find(services().counted("HistoryLonger", 2)) != std::string::npos); + REQUIRE(middle.find(said("HistoryCurrent")) == std::string::npos); + } + { + INFO("the first kept says nothing of one before"); + REQUIRE(studio.candidates[2].detail.find(said("HistorySameLength")) == std::string::npos); + REQUIRE(studio.candidates[2].detail.find("longer") == std::string::npos); + } } - template<> template<> - void alscriptstudiohistory_object::test<3>() + TEST_CASE_METHOD(alscriptstudiohistory_data, "alscriptstudiohistory: a chosen save is compared with the tab now or once the tab has loaded", "[alscriptstudiohistory]") { - set_test_name("a save chosen is compared with the tab now, and offered back; before the tab has loaded, once it has"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door", "the text now"); - saved(doc, "the text then", 100.0); + require_saved(doc, "the text then", 100.0); unit.show(doc); studio.choose(studio.candidates[0].value); - ensure_equals("compared", studio.did.size(), 1U); + UNSCOPED_INFO("compared"); + REQUIRE(studio.did.size() == 1U); LLStringUtil::format_map_t when; when["[WHEN]"] = studio.candidates[0].label; when["[NAME]"] = "door"; - ensure_equals("then beside now", studio.did[0], + UNSCOPED_INFO("then beside now"); + CHECK(studio.did[0] == "compare door: the text then | the text now (" + said("HistorySavedAt", when) + " | " + said("CompareNow") + ")"); - ensure("held to offer back", doc.historyShown && doc.historyShown->text == "the text then"); - ensure("the notice said again", studio.notices == 1); + { + INFO("held to offer back"); + REQUIRE(doc.historyShown); + CHECK(doc.historyShown->text == "the text then"); + } + UNSCOPED_INFO("the notice said again"); + CHECK(studio.notices == 1); Doc& loading = tab("lamp", "", false); - saved(loading, "a lamp's text", 50.0); + require_saved(loading, "a lamp's text", 50.0); unit.show(loading); studio.choose(studio.candidates[0].value); - ensure_equals("not compared yet", studio.did.size(), 1U); - ensure("once it has loaded", loading.pendingCompare && loading.pendingCompare->text == "a lamp's text"); + UNSCOPED_INFO("not compared yet"); + REQUIRE(studio.did.size() == 1U); + { + INFO("once it has loaded"); + REQUIRE(loading.pendingCompare); + CHECK(loading.pendingCompare->text == "a lamp's text"); + } } - template<> template<> - void alscriptstudiohistory_object::test<4>() + TEST_CASE_METHOD(alscriptstudiohistory_data, "alscriptstudiohistory: the notice offers a save back only where the tab may be changed", "[alscriptstudiohistory]") { - set_test_name("the notice offers a save back where the tab may be changed; restored as carried text, the comparison ended"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door", "now"); ALSavedText shown; @@ -249,16 +267,16 @@ namespace tut LLStringUtil::format_map_t args; args["[WHEN]"] = ALRecoveryEntry::sayWhen(shown.when); const ALScriptNoticeBar::Notice notice = ALScriptStudioOrphans::noticeFor(&doc, services()); - ensure_equals("said", notice.text, said("NoticeHistory", args)); - ensure_equals("offered back", notice.buttons[0].first, std::string("restore_saved")); + CHECK(notice.text == said("NoticeHistory", args)); + UNSCOPED_INFO("offered back"); + CHECK(notice.buttons[0].first == std::string("restore_saved")); doc.modifiable = false; - ensure("not where it may not be changed", ALScriptStudioOrphans::noticeFor(&doc, services()).buttons[0].first.empty()); + UNSCOPED_INFO("not where it may not be changed"); + REQUIRE(ALScriptStudioOrphans::noticeFor(&doc, services()).buttons[0].first.empty()); } - template<> template<> - void alscriptstudiohistory_object::test<5>() + TEST_CASE_METHOD(alscriptstudiohistory_data, "alscriptstudiohistory: candidatesOf describes saves with the words and joiner it is given", "[alscriptstudiohistory]") { - set_test_name("a list of saves in a window's own words: each asked by name, with a count where it has one, and joined as it joins"); std::vector saves(3); saves[0].bytes = 10; saves[0].asset = LLUUID::generateNewID(); @@ -279,67 +297,90 @@ namespace tut return out; }; const std::vector listed = ALScriptStudioHistory::candidatesOf(saves, saves[0].asset, words, joined); - ensure_equals("each", listed.size(), 3U); - ensure_equals("the newest", listed[0].detail, std::string("HistoryBytes(10) + HistorySameLength + HistoryCurrent")); - ensure_equals("shorter than the first", listed[1].detail, std::string("HistoryBytes(10) + HistoryShorter(2)")); - ensure_equals("the first says only its length", listed[2].detail, std::string("HistoryBytes(12)")); - ensure_equals("by when", listed[2].label, ALRecoveryEntry::sayWhen(saves[2].when)); - ensure_equals("valued by place", listed[1].value, std::string("1")); + REQUIRE(listed.size() == 3U); + UNSCOPED_INFO("the newest"); + CHECK(listed[0].detail == std::string("HistoryBytes(10) + HistorySameLength + HistoryCurrent")); + UNSCOPED_INFO("shorter than the first"); + CHECK(listed[1].detail == std::string("HistoryBytes(10) + HistoryShorter(2)")); + UNSCOPED_INFO("the first says only its length"); + CHECK(listed[2].detail == std::string("HistoryBytes(12)")); + CHECK(listed[2].label == ALRecoveryEntry::sayWhen(saves[2].when)); + UNSCOPED_INFO("valued by place"); + CHECK(listed[1].value == std::string("1")); } - template<> template<> - void alscriptstudiohistory_object::test<6>() + TEST_CASE_METHOD(alscriptstudiohistory_data, "alscriptstudiohistory: a compared save gets a slider that steps the left through the saves", "[alscriptstudiohistory]") { - set_test_name("a save compared has a slider over the item's saves, oldest first; stepped, the left is that save, its title and the offer back with it"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } studio.host = window.floater; Doc& doc = tab("door", "now\nsame"); - saved(doc, "first\nsame", 300.0); - saved(doc, "second\nsame", 200.0); - saved(doc, "third\nsame", 100.0); + require_saved(doc, "first\nsame", 300.0); + require_saved(doc, "second\nsame", 200.0); + require_saved(doc, "third\nsame", 100.0); unit.show(doc); // Listed newest first: the middle one. studio.choose(studio.candidates[1].value); ALDiffView& view = *doc.compareView; - ensure("the slider", view.bar()->getChild("versions")->getVisible()); - ensure_equals("at the middle of three, oldest first", view.bar()->versionShown(), 1); - ensure_equals("the second", view.leftText(), std::string("second\nsame")); + UNSCOPED_INFO("the slider"); + CHECK(view.bar()->getChild("versions")->getVisible()); + UNSCOPED_INFO("at the middle of three, oldest first"); + CHECK(view.bar()->versionShown() == 1); + CHECK(view.leftText() == std::string("second\nsame")); const S32 notices = studio.notices; ALViewType::as(view.bar()->getChild("newer"))->press(); - ensure_equals("the newest on the left", view.leftText(), std::string("third\nsame")); - ensure("held to offer back", doc.historyShown && doc.historyShown->text == "third\nsame"); + UNSCOPED_INFO("the newest on the left"); + CHECK(view.leftText() == std::string("third\nsame")); + { + INFO("held to offer back"); + REQUIRE(doc.historyShown); + CHECK(doc.historyShown->text == "third\nsame"); + } const std::vector listed = history->list(ALScriptStudioHistory::keyOf(doc)); LLStringUtil::format_map_t when; when["[NAME]"] = "door"; when["[WHEN]"] = ALRecoveryEntry::sayWhen(listed[0].when); - ensure_equals("titled by it", doc.compareTitles->theirs, said("HistorySavedAt", when)); - ensure("said again", !studio.retitled.empty() && studio.retitled.back() == said("HistorySavedAt", when) && studio.notices == notices + 1); - ensure_equals("the right still the tab", view.rightText(), std::string("now\nsame")); + UNSCOPED_INFO("titled by it"); + CHECK(doc.compareTitles->theirs == said("HistorySavedAt", when)); + { + INFO("said again"); + REQUIRE_FALSE(studio.retitled.empty()); + CHECK(studio.retitled.back() == said("HistorySavedAt", when)); + CHECK(studio.notices == notices + 1); + } + UNSCOPED_INFO("the right still the tab"); + CHECK(view.rightText() == std::string("now\nsame")); ALViewType::as(view.bar()->getChild("older"))->press(); ALViewType::as(view.bar()->getChild("older"))->press(); - ensure("the oldest", view.leftText() == "first\nsame" && doc.historyShown->text == "first\nsame" && view.bar()->versionShown() == 0); + { + INFO("the oldest"); + CHECK(view.leftText() == "first\nsame"); + CHECK(doc.historyShown->text == "first\nsame"); + CHECK(view.bar()->versionShown() == 0); + } } - template<> template<> - void alscriptstudiohistory_object::test<7>() + TEST_CASE_METHOD(alscriptstudiohistory_data, "alscriptstudiohistory: a save chosen before load gets its slider when shown and a lone one none", "[alscriptstudiohistory]") { - set_test_name("a save chosen before its tab has loaded gets its slider once the comparison is shown; one save alone, none"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } studio.host = window.floater; Doc& loading = tab("lamp", "", false); - saved(loading, "one", 200.0); - saved(loading, "two", 100.0); + require_saved(loading, "one", 200.0); + require_saved(loading, "two", 100.0); unit.show(loading); studio.choose(studio.candidates[0].value); - ensure("waiting, with something to do once shown", loading.pendingCompare && loading.pendingCompare->shown); + { + INFO("waiting, with something to do once shown"); + REQUIRE(loading.pendingCompare); + REQUIRE(loading.pendingCompare->shown); + } // As the window does once the tab has loaded. loading.loaded = true; loading.editor->setText("two"); @@ -347,32 +388,38 @@ namespace tut loading.pendingCompare.reset(); studio.compareWithTab(loading, pending.text, pending.theirTitle, pending.ownTitle, pending.ranges); pending.shown(loading); - ensure("the slider, at the newest", loading.compareView->bar()->getChild("versions")->getVisible() && - loading.compareView->bar()->versionShown() == 1); + { + INFO("the slider, at the newest"); + CHECK(loading.compareView->bar()->getChild("versions")->getVisible()); + CHECK(loading.compareView->bar()->versionShown() == 1); + } Doc& alone = tab("gate", "now"); - saved(alone, "only", 100.0); + require_saved(alone, "only", 100.0); unit.show(alone); studio.choose(studio.candidates[0].value); - ensure("one save: no slider", alone.compareView && !alone.compareView->bar()->getChild("versions")->getVisible()); + { + INFO("one save: no slider"); + REQUIRE(alone.compareView); + CHECK_FALSE(alone.compareView->bar()->getChild("versions")->getVisible()); + } } - template<> template<> - void alscriptstudiohistory_object::test<8>() + TEST_CASE_METHOD(alscriptstudiohistory_data, "alscriptstudiohistory: stepping to an unreadable save says so and puts the slider back", "[alscriptstudiohistory]") { - set_test_name("a save stepped to that can no longer be read: said, the slider put back, the left as it was"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } studio.host = window.floater; Doc& doc = tab("door", "now"); - saved(doc, "older", 200.0); - saved(doc, "newer", 100.0); + require_saved(doc, "older", 200.0); + require_saved(doc, "newer", 100.0); unit.show(doc); studio.choose(studio.candidates[0].value); ALDiffView& view = *doc.compareView; - ensure_equals("at the newest", view.bar()->versionShown(), 1); + UNSCOPED_INFO("at the newest"); + CHECK(view.bar()->versionShown() == 1); // The older one gone from disk since it was listed. const std::vector listed = history->list(ALScriptStudioHistory::keyOf(doc)); std::error_code gone; @@ -381,60 +428,77 @@ namespace tut LLStringUtil::format_map_t args; args["[NAME]"] = "door"; args["[WHEN]"] = ALRecoveryEntry::sayWhen(listed[1].when); - ensure_equals("said", services().statuses.back(), said("HistoryUnreadable", args)); - ensure("the slider put back", view.bar()->versionShown() == 1); - ensure("the left as it was", view.leftText() == "newer" && doc.historyShown->text == "newer"); + CHECK(services().statuses.back() == said("HistoryUnreadable", args)); + UNSCOPED_INFO("the slider put back"); + CHECK(view.bar()->versionShown() == 1); + { + INFO("the left as it was"); + CHECK(view.leftText() == "newer"); + CHECK(doc.historyShown->text == "newer"); + } } - template<> template<> - void alscriptstudiohistory_object::test<9>() + TEST_CASE_METHOD(alscriptstudiohistory_data, "alscriptstudiohistory: a save stepped to is read once and kept after its file is gone", "[alscriptstudiohistory]") { - set_test_name("a save stepped to is read once and kept: stepped back to after its file is gone, still there"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } studio.host = window.floater; Doc& doc = tab("door", "now"); - saved(doc, "first", 300.0); - saved(doc, "second", 200.0); - saved(doc, "third", 100.0); + require_saved(doc, "first", 300.0); + require_saved(doc, "second", 200.0); + require_saved(doc, "third", 100.0); unit.show(doc); studio.choose(studio.candidates[1].value); ALDiffView& view = *doc.compareView; const auto press = [&view](const char* name) { ALViewType::as(view.bar()->getChild(name))->press(); }; press("older"); - ensure_equals("the oldest", view.leftText(), std::string("first")); + UNSCOPED_INFO("the oldest"); + CHECK(view.leftText() == std::string("first")); press("newer"); const std::vector listed = history->list(ALScriptStudioHistory::keyOf(doc)); std::error_code gone; std::filesystem::remove(fsyspath(listed[2].path), gone); press("older"); - ensure("the oldest again, as it was read", view.leftText() == "first" && view.bar()->versionShown() == 0 && doc.historyShown->text == "first"); + { + INFO("the oldest again, as it was read"); + CHECK(view.leftText() == "first"); + CHECK(view.bar()->versionShown() == 0); + CHECK(doc.historyShown->text == "first"); + } } - template<> template<> - void alscriptstudiohistory_object::test<10>() + TEST_CASE_METHOD(alscriptstudiohistory_data, "alscriptstudiohistory: letting go of a save as its comparison is left removes its slider", "[alscriptstudiohistory]") { - set_test_name("a save let go of as its comparison is left takes its slider with it: shown again, nothing steps a left that is no longer the save's"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } studio.host = window.floater; Doc& doc = tab("door", "now"); - saved(doc, "first", 300.0); - saved(doc, "second", 200.0); + require_saved(doc, "first", 300.0); + require_saved(doc, "second", 200.0); unit.show(doc); studio.choose(studio.candidates[0].value); ALDiffView& view = *doc.compareView; - ensure("the slider", view.bar()->getChild("versions")->getVisible() && view.bar()->versionCount() == 2); + { + INFO("the slider"); + CHECK(view.bar()->getChild("versions")->getVisible()); + CHECK(view.bar()->versionCount() == 2); + } // As leaving the comparison does: Escape, or a keystroke typed into // the source. ALScriptStudioHistory::letGo(doc); - ensure("let go of", !doc.historyShown); - ensure("and its slider", view.bar()->versionCount() == 0 && !view.bar()->getChild("versions")->getVisible()); + UNSCOPED_INFO("let go of"); + REQUIRE_FALSE(doc.historyShown); + { + INFO("and its slider"); + REQUIRE(view.bar()->versionCount() == 0); + CHECK_FALSE(view.bar()->getChild("versions")->getVisible()); + } ALViewType::as(view.bar()->getChild("older"))->press(); - ensure_equals("nothing stepped", view.leftText(), std::string("second")); + UNSCOPED_INFO("nothing stepped"); + CHECK(view.leftText() == std::string("second")); } } diff --git a/indra/newview/tests/alscriptstudiomasters_test.cpp b/indra/newview/tests/alscriptstudiomasters_test.cpp index 8c163bbcf2..b6e84bf597 100644 --- a/indra/newview/tests/alscriptstudiomasters_test.cpp +++ b/indra/newview/tests/alscriptstudiomasters_test.cpp @@ -28,6 +28,7 @@ #include "../alnotecardembedded.h" #include "alfilewrite.h" +#include "almainloop.h" #include "alscriptstudio_fixture.h" #include "alwatchedfile.h" #include "fsyspath.h" @@ -35,7 +36,7 @@ #include "newview_test_fakes.h" #include "workqueue.h" -#include "../test/lltut.h" +#include #include @@ -213,7 +214,7 @@ namespace }; } -namespace tut +namespace { struct alscriptstudiomasters_data { @@ -226,6 +227,9 @@ namespace tut NoWorld world; std::string folder; std::vector wired; + // A look at the tabs waits for the main loop, so every case here + // has one: this file's choice, not whatever a test before it made. + const ALMainLoop::Choice main_loop{ &mainLoop() }; ~alscriptstudiomasters_data() { @@ -242,7 +246,7 @@ namespace tut } } - // The main loop's queue, as the viewer's is, which a look at the + // The queue standing in for the main loop's, which a look at the // tabs waits for: the process's, kept for the rest of the run. static LL::WorkQueue& mainLoop() { return newview_test::mainLoop(); } // Whatever was waiting for the main loop, done. @@ -252,9 +256,8 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } - mainLoop(); const fsyspath made = std::filesystem::temp_directory_path() / fsyspath("alscriptstudiomasters_" + LLUUID::generateNewID().asString()); std::filesystem::create_directories(made); folder = made.string(); @@ -324,73 +327,89 @@ namespace tut typedef std::vector Names; }; - typedef test_group alscriptstudiomasters_group; - typedef alscriptstudiomasters_group::object alscriptstudiomasters_object; - alscriptstudiomasters_group alscriptstudiomasters_instance("alscriptstudiomasters"); - - template<> template<> - void alscriptstudiomasters_object::test<1>() + // A tab may be linked where it is an item's, loaded, may be changed and is linked to + // nothing yet. + TEST_CASE_METHOD(alscriptstudiomasters_data, "alscriptstudiomasters: only a loaded changeable item's unlinked tab may be linked", "[alscriptstudiomasters]") { - set_test_name("a tab may be linked where it is an item's, loaded, may be changed and is linked to nothing yet"); make(); Doc& script = itemTab("door", "default {}\n"); Doc& card = itemTab("card", "notes\n", false, true); - ensure("no tab", !unit->canLink(nullptr)); - ensure("an item's script", unit->canLink(&script)); - ensure("an item's notecard", unit->canLink(&card)); + CHECK_FALSE(unit->canLink(nullptr)); + UNSCOPED_INFO("an item's script"); + CHECK(unit->canLink(&script)); + UNSCOPED_INFO("an item's notecard"); + CHECK(unit->canLink(&card)); script.loaded = false; - ensure("not while it loads", !unit->canLink(&script)); + UNSCOPED_INFO("not while it loads"); + CHECK_FALSE(unit->canLink(&script)); script.loaded = true; script.modifiable = false; - ensure("not where it may not be changed", !unit->canLink(&script)); + UNSCOPED_INFO("not where it may not be changed"); + CHECK_FALSE(unit->canLink(&script)); script.modifiable = true; const ALScriptRef ref = script.ref; script.ref = ALScriptRef(); - ensure("not with no item", !unit->canLink(&script)); + UNSCOPED_INFO("not with no item"); + CHECK_FALSE(unit->canLink(&script)); script.ref = ref; studio->openMasterFile(in("a.lsl"), false); - ensure("not a file's tab", !unit->canLink(services.findDoc("disk:" + in("a.lsl")))); + UNSCOPED_INFO("not a file's tab"); + CHECK_FALSE(unit->canLink(services.findDoc("disk:" + in("a.lsl")))); write(in("door.lsl"), "default {}\n"); linkFromElsewhere(script, in("door.lsl")); - ensure("not once it is linked", !unit->canLink(&script)); - ensure("another file's tab masters nothing", !unit->mastersAny(services.findDoc("disk:" + in("a.lsl")))); + UNSCOPED_INFO("not once it is linked"); + CHECK_FALSE(unit->canLink(&script)); + UNSCOPED_INFO("another file's tab masters nothing"); + CHECK_FALSE(unit->mastersAny(services.findDoc("disk:" + in("a.lsl")))); studio->openMasterFile(in("door.lsl"), false); - ensure("the master's tab masters it", unit->mastersAny(services.findDoc("disk:" + in("door.lsl")))); + UNSCOPED_INFO("the master's tab masters it"); + CHECK(unit->mastersAny(services.findDoc("disk:" + in("door.lsl")))); } - template<> template<> - void alscriptstudiomasters_object::test<2>() + // A notecard carrying items is not linked, and says why, before any file is picked; a script, + // or a notecard carrying none, carries nothing. + TEST_CASE_METHOD(alscriptstudiomasters_data, "alscriptstudiomasters: a notecard carrying items is refused with a reason before any pick", "[alscriptstudiomasters]") { - set_test_name("a notecard carrying items is not linked, and says why, before any file is picked; a script, or a notecard carrying none, carries nothing"); make(); Doc& script = itemTab("door", "default {}\n"); Doc& empty = itemTab("empty", "notes\n", false, true); - ensure("a script carries nothing", !unit->carriesItems(script)); + UNSCOPED_INFO("a script carries nothing"); + CHECK_FALSE(unit->carriesItems(script)); empty.items = std::make_shared(*empty.editor, ALNotecardEmbedded::Holder(), world); - ensure("a notecard with none carries nothing", !unit->carriesItems(empty)); - ensure("and says nothing", services.reports.empty()); + UNSCOPED_INFO("a notecard with none carries nothing"); + CHECK_FALSE(unit->carriesItems(empty)); + UNSCOPED_INFO("and says nothing"); + REQUIRE(services.reports.empty()); Doc& card = itemTab("card", "a b\n", false, true); card.items = std::make_shared(*card.editor, ALNotecardEmbedded::Holder(), world); LLPointer landmark = new LLInventoryItem(); card.items->loaded({ landmark }); - ensure("one with an item carries it", unit->carriesItems(card)); - ensure("said, on its tab, as a failure", says(said().text, "MasterNotecardCarries") && said().failure && said().doc == "card"); + UNSCOPED_INFO("one with an item carries it"); + CHECK(unit->carriesItems(card)); + { + INFO("said, on its tab, as a failure"); + CHECK(says(said().text, "MasterNotecardCarries")); + CHECK(said().failure); + CHECK(said().doc == "card"); + } studio->picked = in("card.txt"); write(in("card.txt"), "a b\n"); unit->linkToFile(card); - ensure_equals("no file asked for", studio->picks, 0); - ensure("nothing linked", !disk.linkOf(card.ref)); + UNSCOPED_INFO("no file asked for"); + CHECK(studio->picks == 0); + UNSCOPED_INFO("nothing linked"); + REQUIRE_FALSE(disk.linkOf(card.ref)); } - template<> template<> - void alscriptstudiomasters_object::test<3>() + // A file picked of another language is refused, and said, before anything is linked: LSL, SLua, + // a notecard's, and a name with no extension. + TEST_CASE_METHOD(alscriptstudiomasters_data, "alscriptstudiomasters: a file picked of another language is refused and its own is linked", "[alscriptstudiomasters]") { - set_test_name("a file picked of another language is refused, and said, before anything is linked: LSL, SLua, a notecard's, and a name with no extension"); make(); Doc& lsl = itemTab("door", "default {}\n"); Doc& slua = itemTab("lamp", "print(1)\n", true); @@ -404,12 +423,18 @@ namespace tut return !disk.linkOf(doc.ref) && services.reports.size() == before + 1 && alscriptstudiomasters_data::says(said().text, word) && said().failure && said().doc == doc.id && services.findDoc(doc.id) == &doc; }; - ensure("SLua for an LSL script", refused(lsl, "door.luau", "MasterNotLSLFile")); - ensure("a notecard's file for an LSL script", refused(lsl, "door.txt", "MasterNotLSLFile")); - ensure("no extension at all", refused(lsl, "door", "MasterNotLSLFile")); - ensure("LSL for an SLua script", refused(slua, "lamp.lsl", "MasterNotSLuaFile")); - ensure("a script's file for a notecard", refused(card, "card.lsl", "MasterNotNotecardFile")); - ensure("nothing closed", studio->closed.empty()); + UNSCOPED_INFO("SLua for an LSL script"); + CHECK(refused(lsl, "door.luau", "MasterNotLSLFile")); + UNSCOPED_INFO("a notecard's file for an LSL script"); + CHECK(refused(lsl, "door.txt", "MasterNotLSLFile")); + UNSCOPED_INFO("no extension at all"); + CHECK(refused(lsl, "door", "MasterNotLSLFile")); + UNSCOPED_INFO("LSL for an SLua script"); + CHECK(refused(slua, "lamp.lsl", "MasterNotSLuaFile")); + UNSCOPED_INFO("a script's file for a notecard"); + CHECK(refused(card, "card.lsl", "MasterNotNotecardFile")); + UNSCOPED_INFO("nothing closed"); + REQUIRE(studio->closed.empty()); // Each of its own language: linked, its tab giving way. const ALScriptRef lsl_ref = lsl.ref, slua_ref = slua.ref, card_ref = card.ref; @@ -425,15 +450,33 @@ namespace tut const std::optional a = disk.linkOf(lsl_ref); const std::optional b = disk.linkOf(slua_ref); const std::optional c = disk.linkOf(card_ref); - ensure("LSL", a && a->master == in("door.lsl") && !a->lua && !a->notecard && a->target == "mono"); - ensure("SLua, by .lua too", b && b->lua && b->target == "luau"); - ensure("a notecard: no language, no target", c && c->notecard && !c->lua && c->target.empty()); + { + INFO("LSL"); + REQUIRE(a); + REQUIRE(a->master == in("door.lsl")); + REQUIRE_FALSE(a->lua); + REQUIRE_FALSE(a->notecard); + CHECK(a->target == "mono"); + } + { + INFO("SLua, by .lua too"); + REQUIRE(b); + REQUIRE(b->lua); + CHECK(b->target == "luau"); + } + { + INFO("a notecard: no language, no target"); + REQUIRE(c); + REQUIRE(c->notecard); + REQUIRE_FALSE(c->lua); + REQUIRE(c->target.empty()); + } } - template<> template<> - void alscriptstudiomasters_object::test<4>() + // A clean tab linked gives way to its file's tab; where the file is what it holds the file's + // stamp is taken, else Send File and Compare are offered there. + TEST_CASE_METHOD(alscriptstudiomasters_data, "alscriptstudiomasters: a linked clean tab gives way and takes the stamp of a matching file", "[alscriptstudiomasters]") { - set_test_name("a clean tab linked: it gives way to its file's tab; where the file is what it holds the file's stamp is taken, else Send File and Compare are offered there"); make(); Doc& same = itemTab("door", "default {}\n"); const LLUUID asset = same.assetId; @@ -441,18 +484,43 @@ namespace tut write(in("door.lsl"), "default {}\n"); studio->picked = in("door.lsl"); unit->linkToFile(same); - ensure("nothing asked", studio->asked.empty()); + REQUIRE(studio->asked.empty()); const std::optional link = disk.linkOf(ref); - ensure("linked as picked, from the world's asset", link && link->made == ALMasterLink::Made::Picked && link->base == asset && - link->itemName == "door" && link->objectName == "Door"); - ensure("the file's stamp taken, as from a send of it", link->stamp != 0 && link->stamp == ALFileStamp::of(in("door.lsl")).time); - ensure("its tab closed, the file's opened in its place", studio->closed == Names{ "door" } && studio->opened == Names{ in("door.lsl") } && - !services.findDoc(ref)); + { + INFO("linked as picked, from the world's asset"); + REQUIRE(link); + REQUIRE(link->made == ALMasterLink::Made::Picked); + REQUIRE(link->base == asset); + REQUIRE(link->itemName == "door"); + CHECK(link->objectName == "Door"); + } + { + INFO("the file's stamp taken, as from a send of it"); + REQUIRE(link->stamp != 0); + CHECK(link->stamp == ALFileStamp::of(in("door.lsl")).time); + } + { + INFO("its tab closed, the file's opened in its place"); + REQUIRE(studio->closed == Names{ "door" }); + CHECK(studio->opened == Names{ in("door.lsl") }); + REQUIRE_FALSE(services.findDoc(ref)); + } Doc* tab = services.findDoc("disk:" + in("door.lsl")); - ensure("said there, with nothing offered", tab && says(said().text, "MasterLinked") && !says(said().text, "MasterLinkDiffers") && - said().doc == tab->id && said().actions.empty()); - ensure("the file's tab acts on the script", tab->master->offerFor == ref); - ensure("nothing written or sent", disk.written.empty() && disk.sent.empty()); + { + INFO("said there, with nothing offered"); + REQUIRE(tab); + CHECK(says(said().text, "MasterLinked")); + CHECK_FALSE(says(said().text, "MasterLinkDiffers")); + CHECK(said().doc == tab->id); + REQUIRE(said().actions.empty()); + } + UNSCOPED_INFO("the file's tab acts on the script"); + CHECK(tab->master->offerFor == ref); + { + INFO("nothing written or sent"); + REQUIRE(disk.written.empty()); + REQUIRE(disk.sent.empty()); + } Doc& other = itemTab("lamp", "default { state_entry() {} }\n"); write(in("lamp.lsl"), "default {}\n"); @@ -460,17 +528,25 @@ namespace tut const ALScriptRef other_ref = other.ref; unit->linkToFile(other); const std::optional differing = disk.linkOf(other_ref); - ensure("a file it is not what the script holds: no stamp", differing && differing->stamp == 0); + { + INFO("a file it is not what the script holds: no stamp"); + REQUIRE(differing); + CHECK(differing->stamp == 0); + } Doc* lamp = services.findDoc("disk:" + in("lamp.lsl")); - ensure("a file it is not: said, Send File and Compare offered on the file's tab", - lamp && says(said().text, "MasterLinkDiffers") && said().doc == lamp->id && - said().actions == Names{ "master_send", "master_compare" }); + { + INFO("a file it is not: said, Send File and Compare offered on the file's tab"); + REQUIRE(lamp); + CHECK(says(said().text, "MasterLinkDiffers")); + CHECK(said().doc == lamp->id); + CHECK(said().actions == Names{ "master_send", "master_compare" }); + } } - template<> template<> - void alscriptstudiomasters_object::test<5>() + // A tab with unsaved changes linked is asked first: Cancel links nothing, Discard holds the + // file up to the text as last saved, Write puts what was typed in the file. + TEST_CASE_METHOD(alscriptstudiomasters_data, "alscriptstudiomasters: linking a tab with unsaved changes asks to cancel or discard or write", "[alscriptstudiomasters]") { - set_test_name("a tab with unsaved changes linked: asked first; Cancel links nothing, Discard holds the file up to the text as last saved, Write puts what was typed in the file"); make(); // Cancel: nothing. Doc& door = itemTab("door", "default {}\n"); @@ -479,10 +555,16 @@ namespace tut studio->picked = in("door.lsl"); studio->answer = Unsaved::Cancel; unit->linkToFile(door); - ensure("asked, the file named", studio->asked == Names{ "door " + in("door.lsl") }); - ensure("Cancel: not linked, the tab kept with what was typed, the file as it was", - !disk.linkOf(door.ref) && services.findDoc("door") == &door && door.editor->isDirty() && - contents(in("door.lsl")) == "default {}\n" && studio->closed.empty()); + UNSCOPED_INFO("asked, the file named"); + CHECK(studio->asked == Names{ "door " + in("door.lsl") }); + { + INFO("Cancel: not linked, the tab kept with what was typed, the file as it was"); + REQUIRE_FALSE(disk.linkOf(door.ref)); + REQUIRE(services.findDoc("door") == &door); + CHECK(door.editor->isDirty()); + CHECK(contents(in("door.lsl")) == "default {}\n"); + REQUIRE(studio->closed.empty()); + } // Discard, the file what the world holds: as a clean tab is linked, // whatever was typed. @@ -490,10 +572,22 @@ namespace tut studio->answer = Unsaved::Discard; unit->linkToFile(door); std::optional link = disk.linkOf(door_ref); - ensure("Discard: linked, the tab gone", link && studio->closed == Names{ "door" }); - ensure("the file is what the tab last saved: its stamp taken, nothing offered", - link->stamp == ALFileStamp::of(in("door.lsl")).time && !says(said().text, "MasterLinkDiffers") && said().actions.empty()); - ensure("the file untouched", contents(in("door.lsl")) == "default {}\n" && disk.written.empty()); + { + INFO("Discard: linked, the tab gone"); + REQUIRE(link); + CHECK(studio->closed == Names{ "door" }); + } + { + INFO("the file is what the tab last saved: its stamp taken, nothing offered"); + CHECK(link->stamp == ALFileStamp::of(in("door.lsl")).time); + CHECK_FALSE(says(said().text, "MasterLinkDiffers")); + REQUIRE(said().actions.empty()); + } + { + INFO("the file untouched"); + CHECK(contents(in("door.lsl")) == "default {}\n"); + REQUIRE(disk.written.empty()); + } // Discard, the file what was typed and is let go of: not what the // world holds. @@ -504,8 +598,13 @@ namespace tut const ALScriptRef lamp_ref = lamp.ref; unit->linkToFile(lamp); link = disk.linkOf(lamp_ref); - ensure("the file held up to the saved text, not to what was dropped: it differs, no stamp, Send File and Compare offered", - link && link->stamp == 0 && says(said().text, "MasterLinkDiffers") && said().actions == Names{ "master_send", "master_compare" }); + { + INFO("the file held up to the saved text, not to what was dropped: it differs, no stamp, Send File and Compare offered"); + REQUIRE(link); + CHECK(link->stamp == 0); + CHECK(says(said().text, "MasterLinkDiffers")); + CHECK(said().actions == Names{ "master_send", "master_compare" }); + } // Write: what was typed in the file first, which the world does not // hold. @@ -517,19 +616,31 @@ namespace tut const ALScriptRef bell_ref = bell.ref; unit->linkToFile(bell); link = disk.linkOf(bell_ref); - ensure_equals("Write: the file holds what was typed", contents(in("bell.lsl")), std::string("default {}\n// mine\n")); - ensure("the write the studio's own", disk.written == Names{ in("bell.lsl") }); - ensure("linked, differing from the world: no stamp, Send File and Compare offered", - link && link->stamp == 0 && says(said().text, "MasterLinkDiffers") && said().actions == Names{ "master_send", "master_compare" }); - ensure("its tab gone", !services.findDoc(bell_ref)); + UNSCOPED_INFO("Write: the file holds what was typed"); + CHECK(contents(in("bell.lsl")) == std::string("default {}\n// mine\n")); + UNSCOPED_INFO("the write the studio's own"); + CHECK(disk.written == Names{ in("bell.lsl") }); + { + INFO("linked, differing from the world: no stamp, Send File and Compare offered"); + REQUIRE(link); + CHECK(link->stamp == 0); + CHECK(says(said().text, "MasterLinkDiffers")); + CHECK(said().actions == Names{ "master_send", "master_compare" }); + } + UNSCOPED_INFO("its tab gone"); + REQUIRE_FALSE(services.findDoc(bell_ref)); Doc* tab = services.findDoc("disk:" + in("bell.lsl")); - ensure("the file's tab holds what was typed", tab && tab->editor->wholeText() == "default {}\n// mine\n"); + { + INFO("the file's tab holds what was typed"); + REQUIRE(tab); + CHECK(tab->editor->wholeText() == "default {}\n// mine\n"); + } } - template<> template<> - void alscriptstudiomasters_object::test<6>() + // Linked from elsewhere: a clean tab gives way to its file's once the links have changed; one + // with something typed is kept and told once, with Compare with File, Open File and Unlink. + TEST_CASE_METHOD(alscriptstudiomasters_data, "alscriptstudiomasters: a link from elsewhere closes a clean tab and tells a typed one once", "[alscriptstudiomasters]") { - set_test_name("linked from elsewhere: a clean tab gives way to its file's once the links have changed; one with something typed is kept and told once, with Compare with File, Open File and Unlink"); make(); Doc& clean = itemTab("door", "default {}\n"); Doc& dirty = itemTab("lamp", "default {}\n"); @@ -539,41 +650,60 @@ namespace tut write(in("lamp.lsl"), "default {}\n"); linkFromElsewhere(clean, in("door.lsl")); linkFromElsewhere(dirty, in("lamp.lsl")); - ensure("nothing done while whoever changed the links is", studio->closed.empty() && services.reports.empty()); + { + INFO("nothing done while whoever changed the links is"); + REQUIRE(studio->closed.empty()); + REQUIRE(services.reports.empty()); + } settle(); - ensure("the clean one gave way to its file's", studio->closed == Names{ "door" } && !services.findDoc(clean_ref) && - services.findDoc("disk:" + in("door.lsl"))); + { + INFO("the clean one gave way to its file's"); + CHECK(studio->closed == Names{ "door" }); + REQUIRE_FALSE(services.findDoc(clean_ref)); + REQUIRE(services.findDoc("disk:" + in("door.lsl"))); + } const auto told = [&](const std::string& id) { return std::count_if(services.reports.begin(), services.reports.end(), [&](const al_studio_test::FakeServices::Said& one) { return one.doc == id && alscriptstudiomasters_data::says(one.text, "MasterLinkedUnsaved"); }); }; - ensure_equals("the one with something typed told once", told("lamp"), 1); + UNSCOPED_INFO("the one with something typed told once"); + REQUIRE(told("lamp") == 1); const al_studio_test::FakeServices::Said& lamp = *std::find_if(services.reports.begin(), services.reports.end(), [](const al_studio_test::FakeServices::Said& one) { return one.doc == "lamp"; }); - ensure("as a failure, with its file to compare with and to open, and the link to let go of", - lamp.failure && lamp.actions == Names{ "master_compare_file", "master_open_file", "master_unlink" }); - ensure("and kept", services.findDoc("lamp") == &dirty && dirty.editor->isDirty()); + { + INFO("as a failure, with its file to compare with and to open, and the link to let go of"); + CHECK(lamp.failure); + CHECK(lamp.actions == Names{ "master_compare_file", "master_open_file", "master_unlink" }); + } + { + INFO("and kept"); + REQUIRE(services.findDoc("lamp") == &dirty); + CHECK(dirty.editor->isDirty()); + } // The links changing again: not told again. Doc& bell = itemTab("bell", "default {}\n"); write(in("bell.lsl"), "default {}\n"); linkFromElsewhere(bell, in("bell.lsl")); settle(); - ensure_equals("told once only", told("lamp"), 1); - ensure("the next clean one gave way", !services.findDoc("bell")); + UNSCOPED_INFO("told once only"); + CHECK(told("lamp") == 1); + UNSCOPED_INFO("the next clean one gave way"); + REQUIRE_FALSE(services.findDoc("bell")); // A tab of a file gone, or of a link held, is left as it is. Doc& gone = itemTab("gone", "default {}\n"); linkFromElsewhere(gone, in("nowhere.lsl")); settle(); - ensure("a file gone: left", services.findDoc("gone") == &gone); + UNSCOPED_INFO("a file gone: left"); + CHECK(services.findDoc("gone") == &gone); } - template<> template<> - void alscriptstudiomasters_object::test<7>() + // A tab kept for what was typed gives way once that is saved -- its file's tab offering Send + // File and Compare where the world now differs -- or reverted. + TEST_CASE_METHOD(alscriptstudiomasters_data, "alscriptstudiomasters: a tab kept for what was typed gives way once saved or reverted", "[alscriptstudiomasters]") { - set_test_name("a tab kept for what was typed gives way once that is saved -- its file's tab offering Send File and Compare where the world now differs -- or reverted"); make(); Doc& door = itemTab("door", "default {}\n"); type(door, "// mine\n"); @@ -581,20 +711,32 @@ namespace tut const ALScriptRef door_ref = door.ref; ALMasterLink link = linkFromElsewhere(door, in("door.lsl")); settle(); - ensure("kept", services.findDoc("door") == &door); + UNSCOPED_INFO("the tab with something typed is kept"); + CHECK(services.findDoc("door") == &door); // Saved from here: the world holds what was typed, and the save // heard marks the link differing. door.editor->resetDirty(); link.state = ALMasterLink::State::Differing; disk.link(link); - ensure("not while the save is still being heard", services.findDoc("door") == &door); + UNSCOPED_INFO("not while the save is still being heard"); + CHECK(services.findDoc("door") == &door); settle(); - ensure("clean: it gave way, whatever it was told", !services.findDoc(door_ref) && studio->closed == Names{ "door" }); + { + INFO("clean: it gave way, whatever it was told"); + REQUIRE_FALSE(services.findDoc(door_ref)); + CHECK(studio->closed == Names{ "door" }); + } Doc* tab = services.findDoc("disk:" + in("door.lsl")); - ensure("its file's tab says so, offering to send the file over the world's or to compare them", - tab && said().doc == tab->id && says(said().text, "MasterGaveWay") && says(said().text, "MasterLinkDiffers") && - said().actions == Names{ "master_send", "master_compare" } && tab->master->offerFor == door_ref); + { + INFO("its file's tab says so, offering to send the file over the world's or to compare them"); + REQUIRE(tab); + REQUIRE(said().doc == tab->id); + CHECK(says(said().text, "MasterGaveWay")); + CHECK(says(said().text, "MasterLinkDiffers")); + CHECK(said().actions == Names{ "master_send", "master_compare" }); + CHECK(tab->master->offerFor == door_ref); + } // Saved with what the file last sent: the links do not change, and // the save heard looks again itself. @@ -608,15 +750,20 @@ namespace tut saved.ref = lamp.ref; unit->saved(saved); settle(); - ensure("gave way on the save alone", !services.findDoc(ALScriptRef(lamp_link.object, lamp_link.item))); - ensure("nothing offered: the world holds what the file does", says(said().text, "MasterGaveWay") && said().actions.empty()); + UNSCOPED_INFO("gave way on the save alone"); + REQUIRE_FALSE(services.findDoc(ALScriptRef(lamp_link.object, lamp_link.item))); + { + INFO("nothing offered: the world holds what the file does"); + CHECK(says(said().text, "MasterGaveWay")); + REQUIRE(said().actions.empty()); + } // A save of a script with no tab here, or not linked: nothing. saved.ref = ALScriptRef(LLUUID::generateNewID(), LLUUID::generateNewID()); const size_t before = services.reports.size(); unit->saved(saved); settle(); - ensure("nothing", services.reports.size() == before); + REQUIRE(services.reports.size() == before); // Reverted: loaded again, clean. Doc& bell = itemTab("bell", "default {}\n"); @@ -627,13 +774,17 @@ namespace tut bell.editor->setText("default {}\n"); unit->loaded(bell); settle(); - ensure("reverted: gave way", !services.findDoc(ALScriptRef(bell_link.object, bell_link.item)) && says(said().text, "MasterGaveWay")); + { + INFO("reverted: gave way"); + REQUIRE_FALSE(services.findDoc(ALScriptRef(bell_link.object, bell_link.item))); + CHECK(says(said().text, "MasterGaveWay")); + } } - template<> template<> - void alscriptstudiomasters_object::test<8>() + // The offers on a tab kept for what was typed: its file on disk set beside it, its file's tab + // opened beside it, the link let go of. + TEST_CASE_METHOD(alscriptstudiomasters_data, "alscriptstudiomasters: a kept tab's offers compare with its file or open it or unlink it", "[alscriptstudiomasters]") { - set_test_name("the offers on a tab kept for what was typed: its file on disk set beside it, its file's tab opened beside it, the link let go of"); make(); Doc& door = itemTab("door", "default {}\n"); type(door, "// mine\n"); @@ -642,38 +793,65 @@ namespace tut settle(); unit->offer(door, "master_compare_file"); - ensure_equals("compared once", studio->compared.size(), size_t(1)); + REQUIRE(studio->compared.size() == size_t(1)); const FakeMastersWindow::Compared& compared = studio->compared.back(); - ensure("in the tab's place: the file on disk, then what was typed", compared.doc == "door" && - compared.left == "default { touch_start(integer n) {} }\n" && - compared.right == "default {}\n// mine\n"); - ensure("each named", says(compared.leftTitle, "CompareMasterOnDisk") && says(compared.leftTitle, "door.lsl") && - says(compared.rightTitle, "CompareNow")); + { + INFO("in the tab's place: the file on disk, then what was typed"); + CHECK(compared.doc == "door"); + CHECK(compared.left == "default { touch_start(integer n) {} }\n"); + CHECK(compared.right == "default {}\n// mine\n"); + } + { + INFO("each named"); + CHECK(says(compared.leftTitle, "CompareMasterOnDisk")); + CHECK(says(compared.leftTitle, "door.lsl")); + CHECK(says(compared.rightTitle, "CompareNow")); + } unit->offer(door, "master_open_file"); Doc* tab = services.findDoc("disk:" + in("door.lsl")); - ensure("the file's tab opened, this one kept", tab && services.findDoc("door") == &door && studio->closed.empty()); - ensure("the file's tab acts on the script", tab->master->offerFor == door.ref); + { + INFO("the file's tab opened, this one kept"); + REQUIRE(tab); + CHECK(services.findDoc("door") == &door); + REQUIRE(studio->closed.empty()); + } + UNSCOPED_INFO("the file's tab acts on the script"); + CHECK(tab->master->offerFor == door.ref); std::filesystem::remove(fsyspath(in("door.lsl"))); unit->offer(door, "master_compare_file"); - ensure("a file that cannot be read: said, nothing compared", studio->compared.size() == 1 && says(said().text, "MasterNotRead") && said().failure); + { + INFO("a file that cannot be read: said, nothing compared"); + REQUIRE(studio->compared.size() == 1); + CHECK(says(said().text, "MasterNotRead")); + CHECK(said().failure); + } // Not offered on a file's own tab. unit->offer(*tab, "master_compare_file"); unit->offer(*tab, "master_open_file"); - ensure("nothing from a file's tab", studio->compared.size() == 1 && studio->opened.size() == 1); + { + INFO("nothing from a file's tab"); + REQUIRE(studio->compared.size() == 1); + REQUIRE(studio->opened.size() == 1); + } unit->offer(door, "master_unlink"); - ensure("let go of, and said", !disk.linkOf(door.ref) && says(said().text, "MasterUnlinked")); + { + INFO("let go of, and said"); + REQUIRE_FALSE(disk.linkOf(door.ref)); + CHECK(says(said().text, "MasterUnlinked")); + } unit->offer(door, "master_compare_file"); - ensure("nothing to compare once unlinked", studio->compared.size() == 1); + UNSCOPED_INFO("nothing to compare once unlinked"); + REQUIRE(studio->compared.size() == 1); } - template<> template<> - void alscriptstudiomasters_object::test<9>() + // The file a script names is linked by Link while nothing is typed in its tab; with something + // typed, it is to be saved or reverted first, and nothing is written. + TEST_CASE_METHOD(alscriptstudiomasters_data, "alscriptstudiomasters: the Link offer links a script's named file only while nothing is typed", "[alscriptstudiomasters]") { - set_test_name("the file a script names is linked by Link while nothing is typed in its tab; with something typed, it is to be saved or reverted first, and nothing is written"); make(); Doc& door = itemTab("door", "default {}\n"); write(in("door.lsl"), "default {}\n"); @@ -681,30 +859,53 @@ namespace tut const ALScriptRef door_ref = door.ref; unit->offer(door, "master_link_hint"); const std::optional link = disk.linkOf(door_ref); - ensure("clean: linked, as named", link && link->made == ALMasterLink::Made::Hint && link->master == in("door.lsl")); + { + INFO("clean: linked, as named"); + REQUIRE(link); + REQUIRE(link->made == ALMasterLink::Made::Hint); + CHECK(link->master == in("door.lsl")); + } Doc& lamp = itemTab("lamp", "default {}\n"); type(lamp, "// mine\n"); write(in("lamp.lsl"), "default {}\n"); lamp.master->hinted = in("lamp.lsl"); unit->offer(lamp, "master_link_hint"); - ensure("with something typed: not linked, nothing asked, the tab kept", - !disk.linkOf(lamp.ref) && studio->asked.empty() && services.findDoc("lamp") == &lamp && - lamp.editor->isDirty()); - ensure("said, Link offered again for after", says(said().text, "MasterHintUnsaved") && said().failure && said().doc == "lamp" && - said().actions == Names{ "master_link_hint" }); - ensure("nothing written", contents(in("lamp.lsl")) == "default {}\n" && disk.written.empty()); + { + INFO("with something typed: not linked, nothing asked, the tab kept"); + REQUIRE_FALSE(disk.linkOf(lamp.ref)); + REQUIRE(studio->asked.empty()); + REQUIRE(services.findDoc("lamp") == &lamp); + CHECK(lamp.editor->isDirty()); + } + { + INFO("said, Link offered again for after"); + CHECK(says(said().text, "MasterHintUnsaved")); + CHECK(said().failure); + CHECK(said().doc == "lamp"); + CHECK(said().actions == Names{ "master_link_hint" }); + } + { + INFO("nothing written"); + CHECK(contents(in("lamp.lsl")) == "default {}\n"); + REQUIRE(disk.written.empty()); + } const ALScriptRef lamp_ref = lamp.ref; lamp.editor->resetDirty(); unit->offer(lamp, "master_link_hint"); - ensure("saved: linked, its tab giving way", disk.linkOf(lamp_ref) && !services.findDoc(lamp_ref)); + { + INFO("saved: linked, its tab giving way"); + REQUIRE(disk.linkOf(lamp_ref)); + REQUIRE_FALSE(services.findDoc(lamp_ref)); + } } - template<> template<> - void alscriptstudiomasters_object::test<10>() + // A tab kept for what was typed, made clean by an undo, is kept and offered to switch to + // its file, and told again when typed in; Switch to File gives way, as a revert or a save + // of it does. + TEST_CASE_METHOD(alscriptstudiomasters_data, "alscriptstudiomasters: a kept tab made clean by an undo is offered Switch to File", "[alscriptstudiomasters]") { - set_test_name("a tab kept for what was typed, made clean by an undo, is kept and offered to switch to its file, and told again when typed in; Switch to File gives way, as a revert or a save of it does"); make(); Doc& door = itemTab("door", "default {}\n"); wire(door); @@ -713,45 +914,83 @@ namespace tut const ALScriptRef door_ref = door.ref; linkFromElsewhere(door, in("door.lsl")); settle(); - ensure("kept and told", services.findDoc(door_ref) == &door && says(said().text, "MasterLinkedUnsaved") && said().doc == "door"); + { + INFO("kept and told"); + CHECK(services.findDoc(door_ref) == &door); + CHECK(says(said().text, "MasterLinkedUnsaved")); + CHECK(said().doc == "door"); + } const size_t said_before = services.reports.size(); const S32 notices = studio->notices; door.editor->undo(); - ensure("clean again by the undo", !door.editor->isDirty()); - ensure("not closed under the author", services.findDoc(door_ref) == &door && studio->closed.empty()); - ensure("its notice: nothing unsaved left, Switch to File offered with the rest", - door.offer && says(door.offer->text, "MasterLinkedClean") && - door.offer->actions == Names{ "master_give_way", "master_compare_file", "master_open_file", "master_unlink" } && - studio->notices == notices + 1); - ensure("over the tab alone, not in Output", services.reports.size() == said_before); + UNSCOPED_INFO("clean again by the undo"); + CHECK_FALSE(door.editor->isDirty()); + { + INFO("not closed under the author"); + CHECK(services.findDoc(door_ref) == &door); + REQUIRE(studio->closed.empty()); + } + { + INFO("its notice: nothing unsaved left, Switch to File offered with the rest"); + REQUIRE(door.offer); + REQUIRE(says(door.offer->text, "MasterLinkedClean")); + CHECK(door.offer->actions == Names{ "master_give_way", "master_compare_file", "master_open_file", "master_unlink" }); + CHECK(studio->notices == notices + 1); + } + UNSCOPED_INFO("over the tab alone, not in Output"); + REQUIRE(services.reports.size() == said_before); // The links changing meanwhile: kept still. Doc& bell = itemTab("bell", "default {}\n"); write(in("bell.lsl"), "default {}\n"); linkFromElsewhere(bell, in("bell.lsl")); settle(); - ensure("kept as the links change", services.findDoc(door_ref) == &door && !services.findDoc("bell")); + { + INFO("kept as the links change"); + CHECK(services.findDoc(door_ref) == &door); + REQUIRE_FALSE(services.findDoc("bell")); + } door.editor->redo(); - ensure("typed in again by the redo: the notice back to what was typed", - door.editor->isDirty() && door.offer && says(door.offer->text, "MasterLinkedUnsaved") && - door.offer->actions == Names{ "master_compare_file", "master_open_file", "master_unlink" }); + { + INFO("typed in again by the redo: the notice back to what was typed"); + REQUIRE(door.editor->isDirty()); + REQUIRE(door.offer); + REQUIRE(says(door.offer->text, "MasterLinkedUnsaved")); + CHECK(door.offer->actions == Names{ "master_compare_file", "master_open_file", "master_unlink" }); + } door.editor->undo(); - ensure("and clean again", door.offer && says(door.offer->text, "MasterLinkedClean")); + { + INFO("and clean again"); + REQUIRE(door.offer); + CHECK(says(door.offer->text, "MasterLinkedClean")); + } // Switch to File taken once typed in again: kept, and told so. type(door, "x"); door.offer.reset(); unit->offer(door, "master_give_way"); - ensure("typed in since it was offered: kept, told so", services.findDoc(door_ref) == &door && door.offer && - says(door.offer->text, "MasterLinkedUnsaved")); + { + INFO("typed in since it was offered: kept, told so"); + REQUIRE(services.findDoc(door_ref) == &door); + REQUIRE(door.offer); + CHECK(says(door.offer->text, "MasterLinkedUnsaved")); + } door.editor->undo(); - ensure("clean again", door.offer && says(door.offer->text, "MasterLinkedClean")); + { + INFO("clean again"); + REQUIRE(door.offer); + CHECK(says(door.offer->text, "MasterLinkedClean")); + } unit->offer(door, "master_give_way"); - ensure("Switch to File: the file's tab in its place, said there", - !services.findDoc(door_ref) && studio->closed.back() == "door" && says(said().text, "MasterGaveWay") && - said().doc == "disk:" + in("door.lsl")); + { + INFO("Switch to File: the file's tab in its place, said there"); + REQUIRE_FALSE(services.findDoc(door_ref)); + CHECK(studio->closed.back() == "door"); + CHECK(says(said().text, "MasterGaveWay")); + CHECK(said().doc == "disk:" + in("door.lsl")); + } // Reverted once clean: gives way, as a revert does. Doc& lamp = itemTab("lamp", "default {}\n"); @@ -762,11 +1001,19 @@ namespace tut linkFromElsewhere(lamp, in("lamp.lsl")); settle(); lamp.editor->undo(); - ensure("kept once clean", services.findDoc(lamp_ref) == &lamp && says(lamp.offer->text, "MasterLinkedClean")); + { + INFO("kept once clean"); + REQUIRE(services.findDoc(lamp_ref) == &lamp); + CHECK(says(lamp.offer->text, "MasterLinkedClean")); + } lamp.editor->setText("default {}\n"); unit->loaded(lamp); settle(); - ensure("reverted: gave way", !services.findDoc(lamp_ref) && says(said().text, "MasterGaveWay")); + { + INFO("reverted: gave way"); + REQUIRE_FALSE(services.findDoc(lamp_ref)); + CHECK(says(said().text, "MasterGaveWay")); + } // Saved once clean: gives way, as a save does. Doc& gate = itemTab("gate", "default {}\n"); @@ -777,11 +1024,16 @@ namespace tut linkFromElsewhere(gate, in("gate.lsl")); settle(); gate.editor->undo(); - ensure("kept once clean", services.findDoc(gate_ref) == &gate); + UNSCOPED_INFO("kept once clean"); + CHECK(services.findDoc(gate_ref) == &gate); ALScriptSaved saved; saved.ref = gate_ref; unit->saved(saved); settle(); - ensure("saved: gave way", !services.findDoc(gate_ref) && says(said().text, "MasterGaveWay")); + { + INFO("saved: gave way"); + REQUIRE_FALSE(services.findDoc(gate_ref)); + CHECK(says(said().text, "MasterGaveWay")); + } } } diff --git a/indra/newview/tests/alscriptstudiomerging_test.cpp b/indra/newview/tests/alscriptstudiomerging_test.cpp index a0445ec5f8..98e3df496c 100644 --- a/indra/newview/tests/alscriptstudiomerging_test.cpp +++ b/indra/newview/tests/alscriptstudiomerging_test.cpp @@ -32,7 +32,7 @@ #include "alflatbutton.h" #include "alscriptstudio_fixture.h" -#include "../test/lltut.h" +#include #include @@ -83,7 +83,7 @@ namespace }; } -namespace tut +namespace { struct alscriptstudiomerging_data { @@ -143,156 +143,201 @@ namespace tut LLStringUtil::format_map_t named(const Doc& doc) { return { { "[NAME]", doc.name } }; } }; - typedef test_group alscriptstudiomerging_group; - typedef alscriptstudiomerging_group::object alscriptstudiomerging_object; - tut::alscriptstudiomerging_group alscriptstudiomerging_test("alscriptstudiomerging"); - - template<> template<> - void alscriptstudiomerging_object::test<1>() + TEST_CASE_METHOD(alscriptstudiomerging_data, "alscriptstudiomerging: mergeSaved puts in theirs as one undo step and leaves conflicts to settle", "[alscriptstudiomerging]") { - set_test_name("saved elsewhere, merged: what only theirs changed put in as one step to undo, the rest a merge to settle; take and keep done with"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door"); doc.savedThere = theirs; - ensure("it can be merged", ALScriptStudioMerging::canMerge(doc)); + CHECK(ALScriptStudioMerging::canMerge(doc)); unit->mergeSaved(doc); - ensure_equals("theirs's line put in, ours kept", doc.editor->wholeText(), lines(8, { { 0, "theirs 0" }, { 2, "mine 2" }, { 6, "mine 6" } })); - ensure("compared under the saved elsewhere's title", studio.titles.size() == 1 && studio.titles[0] == said("CompareSavedThere")); - ensure("as a merge", doc.compareView && doc.compareView->merging()); - ensure_equals("one conflict", doc.compareView->conflictCount(), 1); - ensure("take and keep done with", !doc.savedThere && studio.notices == 1); - ensure_equals("said", services().reports.back().text, services().counted("MergeConflicts", 1, named(doc))); + UNSCOPED_INFO("theirs's line put in, ours kept"); + CHECK(doc.editor->wholeText() == lines(8, { { 0, "theirs 0" }, { 2, "mine 2" }, { 6, "mine 6" } })); + { + INFO("compared under the saved elsewhere's title"); + REQUIRE(studio.titles.size() == 1); + CHECK(studio.titles[0] == said("CompareSavedThere")); + } + REQUIRE(doc.compareView); + CHECK(doc.compareView->merging()); + CHECK(doc.compareView->conflictCount() == 1); + { + INFO("take and keep done with"); + REQUIRE_FALSE(doc.savedThere); + CHECK(studio.notices == 1); + } + CHECK(services().reports.back().text == services().counted("MergeConflicts", 1, named(doc))); // Settled from the comparison's bar: an edit of the tab. doc.compareView->right()->setFocus(true); doc.compareView->right()->goTo(ALTextPos(6, 0)); ALViewType::as(doc.compareView->bar()->getChild("keep_both"))->press(); - ensure_equals("mine then theirs", doc.editor->wholeText(), + UNSCOPED_INFO("mine then theirs"); + CHECK(doc.editor->wholeText() == lines(9, { { 0, "theirs 0" }, { 2, "mine 2" }, { 6, "mine 6" }, { 7, "theirs 6" }, { 8, "line 7" } })); - ensure_equals("none left", doc.compareView->conflictCount(), 0); + CHECK(doc.compareView->conflictCount() == 0); // Each a step to undo: the settling, then the merge. doc.editor->undo(); doc.editor->undo(); - ensure_equals("back to ours", doc.editor->wholeText(), ours); + CHECK(doc.editor->wholeText() == ours); } - template<> template<> - void alscriptstudiomerging_object::test<2>() + TEST_CASE_METHOD(alscriptstudiomerging_data, "alscriptstudiomerging: with nothing both changed a merge is clean and said so", "[alscriptstudiomerging]") { - set_test_name("nothing both changed: merged, and said so"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door"); doc.savedThere = lines(8, { { 0, "theirs 0" } }); unit->mergeSaved(doc); - ensure_equals("merged", doc.editor->wholeText(), lines(8, { { 0, "theirs 0" }, { 2, "mine 2" }, { 6, "mine 6" } })); - ensure("no conflicts", doc.compareView->merging() && doc.compareView->conflictCount() == 0); - ensure_equals("said", services().reports.back().text, said("MergeClean", named(doc))); + CHECK(doc.editor->wholeText() == lines(8, { { 0, "theirs 0" }, { 2, "mine 2" }, { 6, "mine 6" } })); + REQUIRE(doc.compareView->merging()); + CHECK(doc.compareView->conflictCount() == 0); + CHECK(services().reports.back().text == said("MergeClean", named(doc))); } - template<> template<> - void alscriptstudiomerging_object::test<3>() + TEST_CASE_METHOD(alscriptstudiomerging_data, "alscriptstudiomerging: with no base or a read-only tab the texts are compared rather than merged", "[alscriptstudiomerging]") { - set_test_name("what both were no longer known, or the tab not to be changed: compared as they are, take and keep still offered, and said why"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door"); doc.savedThere = theirs; doc.editor->undoJournal().markNeverSaved(); unit->mergeSaved(doc); - ensure_equals("nothing put in", doc.editor->wholeText(), ours); - ensure("compared, not merged", doc.compareView && !doc.compareView->merging() && doc.compareView->changeCount() > 0); - ensure("take and keep still offered", doc.savedThere && studio.notices == 0); - ensure_equals("said", services().statuses.back(), said("MergeNoBase", named(doc))); + UNSCOPED_INFO("nothing put in"); + CHECK(doc.editor->wholeText() == ours); + { + INFO("compared, not merged"); + REQUIRE(doc.compareView); + REQUIRE_FALSE(doc.compareView->merging()); + CHECK(doc.compareView->changeCount() > 0); + } + { + INFO("take and keep still offered"); + REQUIRE(doc.savedThere); + CHECK(studio.notices == 0); + } + CHECK(services().statuses.back() == said("MergeNoBase", named(doc))); Doc& locked = tab("lamp"); locked.modifiable = false; locked.savedThere = theirs; - ensure("cannot be merged", !ALScriptStudioMerging::canMerge(locked)); + CHECK_FALSE(ALScriptStudioMerging::canMerge(locked)); unit->mergeSaved(locked); - ensure("compared as they are", locked.editor->wholeText() == ours && locked.compareView && !locked.compareView->merging()); - ensure_equals("said", services().statuses.back(), said("MergeReadOnly", named(locked))); + { + INFO("compared as they are"); + REQUIRE(locked.editor->wholeText() == ours); + REQUIRE(locked.compareView); + CHECK_FALSE(locked.compareView->merging()); + } + CHECK(services().statuses.back() == said("MergeReadOnly", named(locked))); } - template<> template<> - void alscriptstudiomerging_object::test<4>() + TEST_CASE_METHOD(alscriptstudiomerging_data, "alscriptstudiomerging: mergeWorld merges the world's text once loaded unless the unit has gone", "[alscriptstudiomerging]") { - set_test_name("the world's item moved on: asked for, merged once loaded, and the tab made from its asset; nothing once the window has gone"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door"); const LLUUID was = LLUUID::generateNewID(); const LLUUID moved = LLUUID::generateNewID(); doc.assetId = was; unit->mergeWorld(doc); - ensure_equals("asked", studio.worldAsked, std::string("door")); - ensure_equals("nothing yet", doc.editor->wholeText(), ours); + CHECK(studio.worldAsked == std::string("door")); + UNSCOPED_INFO("nothing yet"); + CHECK(doc.editor->wholeText() == ours); studio.world(doc, theirs, moved); - ensure_equals("merged", doc.editor->wholeText(), lines(8, { { 0, "theirs 0" }, { 2, "mine 2" }, { 6, "mine 6" } })); - ensure("under the world's title, as a merge", studio.titles.back() == said("CompareWorld") && doc.compareView->conflictCount() == 1); - ensure("made from what the world holds", doc.assetId == moved); + CHECK(doc.editor->wholeText() == lines(8, { { 0, "theirs 0" }, { 2, "mine 2" }, { 6, "mine 6" } })); + { + INFO("under the world's title, as a merge"); + CHECK(studio.titles.back() == said("CompareWorld")); + CHECK(doc.compareView->conflictCount() == 1); + } + UNSCOPED_INFO("made from what the world holds"); + CHECK(doc.assetId == moved); Doc& lamp = tab("lamp"); lamp.assetId = was; unit->mergeWorld(lamp); unit.reset(); studio.world(lamp, theirs, moved); - ensure("nothing", lamp.editor->wholeText() == ours && lamp.assetId == was); + { + INFO("nothing once the merges are gone"); + CHECK(lamp.editor->wholeText() == ours); + CHECK(lamp.assetId == was); + } } - template<> template<> - void alscriptstudiomerging_object::test<5>() + TEST_CASE_METHOD(alscriptstudiomerging_data, "alscriptstudiomerging: a merge undone before saving brings back the other version and its offer", "[alscriptstudiomerging]") { - set_test_name("a merge undone before it is saved: what was saved elsewhere back, said, and offered again; a step forward leaves it so"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door"); doc.savedThere = theirs; unit->mergeSaved(doc); - ensure("set aside", !doc.savedThere && doc.merged); - ensure("the conflicts said with the comparison to show", services().reports.back().actions == std::vector{ "show_compare" }); + { + INFO("set aside"); + REQUIRE_FALSE(doc.savedThere); + REQUIRE(doc.merged); + } + UNSCOPED_INFO("the conflicts said with the comparison to show"); + CHECK(services().reports.back().actions == std::vector{ "show_compare" }); // Typed in, or any change not a step back: nothing. unit->textChanged(doc); - ensure("still merged", !doc.savedThere && doc.merged); + { + INFO("still merged"); + REQUIRE_FALSE(doc.savedThere); + REQUIRE(doc.merged); + } const S32 notices = studio.notices; doc.editor->undo(); - ensure_equals("back to ours", doc.editor->wholeText(), ours); + CHECK(doc.editor->wholeText() == ours); unit->textChanged(doc); - ensure("what was saved elsewhere back", doc.savedThere && *doc.savedThere == theirs && !doc.merged); - ensure_equals("said", services().reports.back().text, said("MergeTakenBack", named(doc))); - ensure("take, keep, merge and compare offered again", - services().reports.back().actions == std::vector{ "take_saved", "keep_saved", "merge_saved", "compare_saved" }); - ensure("the notice said again", studio.notices > notices); + { + INFO("what was saved elsewhere back"); + REQUIRE(doc.savedThere); + CHECK(*doc.savedThere == theirs); + REQUIRE_FALSE(doc.merged); + } + CHECK(services().reports.back().text == said("MergeTakenBack", named(doc))); + UNSCOPED_INFO("take, keep, merge and compare offered again"); + CHECK(services().reports.back().actions == std::vector{ "take_saved", "keep_saved", "merge_saved", "compare_saved" }); + UNSCOPED_INFO("the notice said again"); + CHECK(studio.notices > notices); // Stepped forward: the merge is back in the text, the other version // still stands until it is merged again. doc.editor->redo(); unit->textChanged(doc); - ensure("left so", doc.savedThere && *doc.savedThere == theirs && !doc.merged); + { + INFO("left so"); + REQUIRE(doc.savedThere); + CHECK(*doc.savedThere == theirs); + REQUIRE_FALSE(doc.merged); + } unit->mergeSaved(doc); - ensure("merged again: set aside again", !doc.savedThere && doc.merged); + { + INFO("merged again: set aside again"); + REQUIRE_FALSE(doc.savedThere); + REQUIRE(doc.merged); + } } - template<> template<> - void alscriptstudiomerging_object::test<6>() + TEST_CASE_METHOD(alscriptstudiomerging_data, "alscriptstudiomerging: a world merge undone puts back the old asset so a save stops for the world", "[alscriptstudiomerging]") { - set_test_name("the world's item merged, then undone: the tab made from the asset it was again, so a save stops for the world"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door"); const LLUUID was = LLUUID::generateNewID(); @@ -300,52 +345,65 @@ namespace tut doc.assetId = was; unit->mergeWorld(doc); studio.world(doc, theirs, moved); - ensure("made from what the world holds", doc.assetId == moved && doc.merged); + { + INFO("made from what the world holds"); + CHECK(doc.assetId == moved); + REQUIRE(doc.merged); + } doc.editor->undo(); unit->textChanged(doc); - ensure("made from what it was again", doc.assetId == was && !doc.merged); - ensure("reload, merge and compare offered again", - services().reports.back().actions == std::vector{ "reload_world", "merge_world", "compare_world" }); + { + INFO("made from what it was again"); + CHECK(doc.assetId == was); + REQUIRE_FALSE(doc.merged); + } + UNSCOPED_INFO("reload, merge and compare offered again"); + CHECK(services().reports.back().actions == std::vector{ "reload_world", "merge_world", "compare_world" }); } - template<> template<> - void alscriptstudiomerging_object::test<7>() + TEST_CASE_METHOD(alscriptstudiomerging_data, "alscriptstudiomerging: settle takes the conflict at the caret and the notice goes with the last", "[alscriptstudiomerging]") { - set_test_name("a conflict settled from the keyboard where the caret is in one; the notice says conflicts are left until none are"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& doc = tab("door"); doc.savedThere = theirs; unit->mergeSaved(doc); // As the window keeps what was said, and shows the comparison. doc.offer = Doc::Offer{ services().reports.back().text, services().reports.back().actions }; - ensure("not while the comparison is not in front", !ALScriptStudioMerging::canSettle(doc)); + UNSCOPED_INFO("not while the comparison is not in front"); + CHECK_FALSE(ALScriptStudioMerging::canSettle(doc)); doc.view = Doc::View::Compare; ALScriptNoticeBar::Notice notice = ALScriptStudioOrphans::noticeFor(&doc, services()); - ensure("the notice offers the comparison", notice.buttons[0].first == "show_compare" && notice.buttons[1].first.empty()); + { + INFO("the notice offers the comparison"); + CHECK(notice.buttons[0].first == "show_compare"); + REQUIRE(notice.buttons[1].first.empty()); + } doc.compareView->right()->setFocus(true); doc.compareView->right()->goTo(ALTextPos(2, 0)); - ensure("not where the caret is in no conflict", !ALScriptStudioMerging::canSettle(doc)); + UNSCOPED_INFO("not where the caret is in no conflict"); + CHECK_FALSE(ALScriptStudioMerging::canSettle(doc)); doc.compareView->right()->goTo(ALTextPos(6, 0)); - ensure("where it is in one", ALScriptStudioMerging::canSettle(doc)); - ensure("settled with theirs", ALScriptStudioMerging::settle(doc, ALTextMerge::Take::Theirs)); - ensure_equals("theirs in the tab", doc.editor->wholeText(), lines(8, { { 0, "theirs 0" }, { 2, "mine 2" }, { 6, "theirs 6" } })); - ensure_equals("none left", doc.compareView->conflictCount(), 0); - ensure("nothing left to settle", !ALScriptStudioMerging::canSettle(doc)); + UNSCOPED_INFO("where it is in one"); + CHECK(ALScriptStudioMerging::canSettle(doc)); + CHECK(ALScriptStudioMerging::settle(doc, ALTextMerge::Take::Theirs)); + UNSCOPED_INFO("theirs in the tab"); + CHECK(doc.editor->wholeText() == lines(8, { { 0, "theirs 0" }, { 2, "mine 2" }, { 6, "theirs 6" } })); + CHECK(doc.compareView->conflictCount() == 0); + CHECK_FALSE(ALScriptStudioMerging::canSettle(doc)); notice = ALScriptStudioOrphans::noticeFor(&doc, services()); - ensure("nor to say", notice.buttons[0].first.empty()); + UNSCOPED_INFO("nor to say"); + REQUIRE(notice.buttons[0].first.empty()); } - template<> template<> - void alscriptstudiomerging_object::test<8>() + TEST_CASE_METHOD(alscriptstudiomerging_data, "alscriptstudiomerging: a merge follows the tab's comparison in letting blank changes go", "[alscriptstudiomerging]") { - set_test_name("a merge begun as the tab's comparison tells lines: blanks let go of there, theirs's change among the lines they re-indented put in, and no conflict said where it finds none"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } // The tab compared before, letting blanks go; then changed line 5 // and re-indented line 7, while what was saved elsewhere re-indented @@ -357,10 +415,13 @@ namespace tut doc.editor->insertText(lines(8, { { 5, "mine 5" }, { 7, " line 7" } })); doc.savedThere = lines(8, { { 2, " line 2" }, { 3, " three" }, { 4, " line 4" }, { 5, " line 5" } }); unit->mergeSaved(doc); - ensure_equals("theirs's line 3 put in, the tab's own kept", doc.editor->wholeText(), + UNSCOPED_INFO("theirs's line 3 put in, the tab's own kept"); + CHECK(doc.editor->wholeText() == lines(8, { { 3, " three" }, { 5, "mine 5" }, { 7, " line 7" } })); - ensure("compared with the tab as merged", doc.compareView->rightText() == doc.editor->wholeText()); - ensure("a merge with no conflict", doc.compareView->merging() && doc.compareView->conflictCount() == 0); - ensure_equals("said", services().reports.back().text, said("MergeClean", named(doc))); + UNSCOPED_INFO("compared with the tab as merged"); + CHECK(doc.compareView->rightText() == doc.editor->wholeText()); + REQUIRE(doc.compareView->merging()); + CHECK(doc.compareView->conflictCount() == 0); + CHECK(services().reports.back().text == said("MergeClean", named(doc))); } } diff --git a/indra/newview/tests/alscriptstudioorphans_test.cpp b/indra/newview/tests/alscriptstudioorphans_test.cpp index f458f2b049..1203f8fccc 100644 --- a/indra/newview/tests/alscriptstudioorphans_test.cpp +++ b/indra/newview/tests/alscriptstudioorphans_test.cpp @@ -31,7 +31,7 @@ #include "llbutton.h" #include "lltextbox.h" -#include "../test/lltut.h" +#include namespace { @@ -94,7 +94,7 @@ namespace } } -namespace tut +namespace { struct alscriptstudioorphans_data { @@ -119,7 +119,7 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } recovery = std::make_unique(services(), &studio); files = std::make_unique(services()); @@ -148,113 +148,148 @@ namespace tut } }; - typedef test_group alscriptstudioorphans_group; - typedef alscriptstudioorphans_group::object alscriptstudioorphans_object; - alscriptstudioorphans_group alscriptstudioorphans_instance("alscriptstudioorphans"); - - template<> template<> - void alscriptstudioorphans_object::test<1>() + TEST_CASE_METHOD(alscriptstudioorphans_data, "alscriptstudioorphans: a tab's orphan kind follows what is in reach", "[alscriptstudioorphans]") { - set_test_name("what a tab has become, as what is in reach says: each kind, and a locked or unloaded text kept so while there is an item"); make(); Doc& o = tab("object"); Reach reach = inReach(); - ensure("in reach: nothing", Orphans::seen(o, reach) == Orphan::None); + UNSCOPED_INFO("in reach: nothing"); + CHECK(Orphans::seen(o, reach) == Orphan::None); reach.objectThere = false; - ensure("its object out of sight", Orphans::seen(o, reach) == Orphan::Away); + UNSCOPED_INFO("its object out of sight"); + CHECK(Orphans::seen(o, reach) == Orphan::Away); reach.objectThere = true; reach.heldByPrim = false; - ensure("its prim holds it no more", Orphans::seen(o, reach) == Orphan::Removed); + UNSCOPED_INFO("its prim holds it no more"); + CHECK(Orphans::seen(o, reach) == Orphan::Removed); reach.heldByPrim.reset(); o.orphan->kind = Orphan::Removed; - ensure("while the prim is asked again, gone stays gone", Orphans::seen(o, reach) == Orphan::Removed); + UNSCOPED_INFO("while the prim is asked again, gone stays gone"); + CHECK(Orphans::seen(o, reach) == Orphan::Removed); o.orphan->kind = Orphan::None; - ensure("and nothing stays nothing", Orphans::seen(o, reach) == Orphan::None); + UNSCOPED_INFO("and nothing stays nothing"); + CHECK(Orphans::seen(o, reach) == Orphan::None); reach.heldByPrim = true; o.orphan->kind = Orphan::Locked; - ensure("a locked text stays locked while its item is there", Orphans::seen(o, reach) == Orphan::Locked); + UNSCOPED_INFO("a locked text stays locked while its item is there"); + CHECK(Orphans::seen(o, reach) == Orphan::Locked); reach.heldByPrim = false; - ensure("but not once it is gone", Orphans::seen(o, reach) == Orphan::Removed); + UNSCOPED_INFO("but not once it is gone"); + CHECK(Orphans::seen(o, reach) == Orphan::Removed); reach.offline = true; - ensure("offline", Orphans::seen(o, reach) == Orphan::Offline); + CHECK(Orphans::seen(o, reach) == Orphan::Offline); Doc& i = tab("inventory", true); Reach item = inReach(); item.trashed = true; - ensure("in the Trash", Orphans::seen(i, item) == Orphan::Trashed); + CHECK(Orphans::seen(i, item) == Orphan::Trashed); i.orphan->kind = Orphan::Unloaded; - ensure("an unloaded text stays so in the Trash", Orphans::seen(i, item) == Orphan::Unloaded); + UNSCOPED_INFO("an unloaded text stays so in the Trash"); + CHECK(Orphans::seen(i, item) == Orphan::Unloaded); item.itemThere = false; - ensure("its item gone", Orphans::seen(i, item) == Orphan::Removed); + UNSCOPED_INFO("its item gone"); + CHECK(Orphans::seen(i, item) == Orphan::Removed); Doc& f = tab("file"); f.ref = ALScriptRef(); f.file = "/somewhere/f.lsl"; Reach file; - ensure("its file gone", Orphans::seen(f, file) == Orphan::FileGone); + CHECK(Orphans::seen(f, file) == Orphan::FileGone); file.fileThere = true; - ensure("its file there", Orphans::seen(f, file) == Orphan::None); + UNSCOPED_INFO("its file there"); + CHECK(Orphans::seen(f, file) == Orphan::None); f.loaded = false; - ensure("not loaded yet: nothing", Orphans::seen(f, Reach()) == Orphan::None); + UNSCOPED_INFO("not loaded yet: nothing"); + CHECK(Orphans::seen(f, Reach()) == Orphan::None); } - template<> template<> - void alscriptstudioorphans_object::test<2>() + TEST_CASE_METHOD(alscriptstudioorphans_data, "alscriptstudioorphans: a failed load makes a tab locked or unloaded or gone or away", "[alscriptstudioorphans]") { - set_test_name("what a failed load makes a tab: locked, unloaded, or gone -- from the inventory, its object, or out of sight"); make(); Doc& o = tab("object"); Doc& i = tab("inventory", true); - ensure("not permitted", Orphans::failedAs(o, Failure::NotPermitted, true) == Orphan::Locked); - ensure("unreadable", Orphans::failedAs(o, Failure::Unreadable, true) == Orphan::Unloaded); - ensure("a fetch that failed", Orphans::failedAs(o, Failure::Fetch, true) == Orphan::Unloaded); - ensure("gone from its object", Orphans::failedAs(o, Failure::Missing, true) == Orphan::Removed); - ensure("its object out of sight", Orphans::failedAs(o, Failure::Missing, false) == Orphan::Away); - ensure("gone from the inventory", Orphans::failedAs(i, Failure::Missing, false) == Orphan::Removed); + CHECK(Orphans::failedAs(o, Failure::NotPermitted, true) == Orphan::Locked); + CHECK(Orphans::failedAs(o, Failure::Unreadable, true) == Orphan::Unloaded); + CHECK(Orphans::failedAs(o, Failure::Fetch, true) == Orphan::Unloaded); + UNSCOPED_INFO("gone from its object"); + CHECK(Orphans::failedAs(o, Failure::Missing, true) == Orphan::Removed); + UNSCOPED_INFO("its object out of sight"); + CHECK(Orphans::failedAs(o, Failure::Missing, false) == Orphan::Away); + UNSCOPED_INFO("gone from the inventory"); + CHECK(Orphans::failedAs(i, Failure::Missing, false) == Orphan::Removed); } - template<> template<> - void alscriptstudioorphans_object::test<3>() + TEST_CASE_METHOD(alscriptstudioorphans_data, "alscriptstudioorphans: the check lets a tab be away a moment and says what was lost or back", "[alscriptstudioorphans]") { - set_test_name("the check: out of sight a moment is not gone; lost with changes, kept and said; back, said; the notice and toolbar told"); Orphans& unit = make(); Doc& a = tab("a"); Doc& b = tab("b"); studio.reaches["a"].objectThere = false; const F64 due = unit.check(); - ensure("each tab's place taken", studio.placed == Names{ "a", "b" }); - ensure("out of sight a moment: not yet away", a.orphan->kind == Orphan::None && a.orphan->awaySince > 0.0); - ensure("and to be looked at again once the moment is past", due > a.orphan->awaySince && due <= a.orphan->awaySince + 3.0); + UNSCOPED_INFO("each tab's place taken"); + CHECK(studio.placed == Names{ "a", "b" }); + { + INFO("out of sight a moment: not yet away"); + CHECK(a.orphan->kind == Orphan::None); + CHECK(a.orphan->awaySince > 0.0); + } + { + INFO("and to be looked at again once the moment is past"); + CHECK(due > a.orphan->awaySince); + CHECK(due <= a.orphan->awaySince + 3.0); + } a.orphan->awaySince -= 5.0; a.orphan->noticeDismissed = true; unit.check(); - ensure("gone a while: away", a.orphan->kind == Orphan::Away && !a.orphan->noticeDismissed && studio.toolbars == 1); + { + INFO("gone a while: away"); + CHECK(a.orphan->kind == Orphan::Away); + CHECK_FALSE(a.orphan->noticeDismissed); + CHECK(studio.toolbars == 1); + } b.editor->setCaret(b.editor->document().end()); b.editor->insertText(" typed"); studio.reaches["b"].heldByPrim = false; unit.check(); - ensure("gone with changes: kept", b.orphan->kind == Orphan::Removed && recovery->keptFor(b.recoveryKey) == 1); - ensure("and said, with what can be done", services().reports.back().text == said("OrphanRemovedKept", "[NAME]", "b") && - services().reports.back().actions == Names{ "copy", "export" }); + { + INFO("gone with changes: kept"); + CHECK(b.orphan->kind == Orphan::Removed); + CHECK(recovery->keptFor(b.recoveryKey) == 1); + } + { + INFO("and said, with what can be done"); + CHECK(services().reports.back().text == said("OrphanRemovedKept", "[NAME]", "b")); + CHECK(services().reports.back().actions == Names{ "copy", "export" }); + } studio.reaches["a"].objectThere = true; unit.check(); - ensure("back: said", a.orphan->kind == Orphan::None && services().reports.back().text == said("OrphanBack", "[NAME]", "a") && - a.orphan->awaySince == 0.0); + { + INFO("back: said"); + CHECK(a.orphan->kind == Orphan::None); + CHECK(services().reports.back().text == said("OrphanBack", "[NAME]", "a")); + CHECK(a.orphan->awaySince == 0.0); + } const size_t said = services().reports.size(); unit.check(); - ensure("nothing changed: nothing said", services().reports.size() == said && studio.toolbars == 3); + { + INFO("nothing changed: nothing said"); + CHECK(services().reports.size() == said); + CHECK(studio.toolbars == 3); + } Doc& c = tab("c"); c.orphan->kind = Orphan::Away; c.orphan->detached = true; c.orphan->reattachTries = ALRecoveryRetry::TRIES; unit.check(); - ensure("a detached one back: nothing said, its load will", c.orphan->kind == Orphan::None && services().reports.size() == said); + { + INFO("a detached one back: nothing said, its load will"); + CHECK(c.orphan->kind == Orphan::None); + CHECK(services().reports.size() == said); + } } - template<> template<> - void alscriptstudioorphans_object::test<4>() + TEST_CASE_METHOD(alscriptstudioorphans_data, "alscriptstudioorphans: a detached tab reloads under its kept text while tries last and are due", "[alscriptstudioorphans]") { - set_test_name("a detached tab loaded under what it holds once its item is in reach, while its tries last and the next is due"); Orphans& unit = make(); Doc& a = tab("a"); a.orphan->detached = true; @@ -264,283 +299,411 @@ namespace tut a.editor->insertText(" text"); a.orphan->reattachTries = ALRecoveryRetry::TRIES; unit.check(); - ensure("tried out: waits for a person", studio.loads.empty()); + UNSCOPED_INFO("tried out: waits for a person"); + CHECK(studio.loads.empty()); a.orphan->reattachTries = 1; a.orphan->nextReattach = LLTimer::getTotalSeconds() + 60.0; const F64 next = unit.check(); - ensure("to be looked at again when its try is due", next == a.orphan->nextReattach); - ensure("not due yet", studio.loads.empty()); + UNSCOPED_INFO("to be looked at again when its try is due"); + CHECK(next == a.orphan->nextReattach); + UNSCOPED_INFO("not due yet"); + CHECK(studio.loads.empty()); a.orphan->nextReattach = 0.0; unit.check(); - ensure("loaded", studio.loads.size() == 1 && studio.loads[0] == a.ref); - ensure("what it holds kept first, and carried over", - recovery->keptFor(a.recoveryKey) == 1 && a.carriedText && *a.carriedText == "kept text"); - ensure("not loaded, and not to be typed in, until it is", !a.loaded && a.editor->isReadOnly() && !a.orphan->detached); + REQUIRE(studio.loads.size() == 1); + CHECK(studio.loads[0] == a.ref); + { + INFO("what it holds kept first, and carried over"); + CHECK(recovery->keptFor(a.recoveryKey) == 1); + REQUIRE(a.carriedText); + CHECK(*a.carriedText == "kept text"); + } + { + INFO("not loaded, and not to be typed in, until it is"); + CHECK_FALSE(a.loaded); + CHECK(a.editor->isReadOnly()); + CHECK_FALSE(a.orphan->detached); + } Doc& u = tab("u"); u.orphan->detached = true; u.orphan->kind = Orphan::Unloaded; u.loadFailure = Failure::Unreadable; studio.reaches["u"].heldByPrim = true; unit.check(); - ensure("an unreadable one is not tried on its own", studio.loads.size() == 1); + UNSCOPED_INFO("an unreadable one is not tried on its own"); + CHECK(studio.loads.size() == 1); u.loadFailure = Failure::Fetch; unit.check(); - ensure("a fetch that failed is", studio.loads.size() == 2); + UNSCOPED_INFO("a fetch that failed is"); + CHECK(studio.loads.size() == 2); } - template<> template<> - void alscriptstudioorphans_object::test<5>() + TEST_CASE_METHOD(alscriptstudioorphans_data, "alscriptstudioorphans: the notice says a kept text first then each kind's words and buttons", "[alscriptstudioorphans]") { - set_test_name("what the notice says: a kept text first, stale where saved since; each kind's words and buttons; nothing once hidden"); make(); const al_studio_test::FakeServices& words = services(); - ensure("no tab: nothing", Orphans::noticeFor(nullptr, words).text.empty()); + UNSCOPED_INFO("no tab: nothing"); + CHECK(Orphans::noticeFor(nullptr, words).text.empty()); Doc& a = tab("a"); - ensure("nothing to say", Orphans::noticeFor(&a, words).text.empty()); + CHECK(Orphans::noticeFor(&a, words).text.empty()); ALRecoveryEntry kept; kept.baseAsset = LLUUID::generateNewID(); a.recoverable = kept; a.orphan->kind = Orphan::Away; ALScriptNoticeBar::Notice notice = Orphans::noticeFor(&a, words); const std::string when = kept.whenSaid(); - ensure("a kept text first", notice.text == said("NoticeRecoverable", "[WHEN]", when) && notice.buttons[0].first == "compare_kept" && - notice.buttons[1].first == "restore" && - notice.buttons[2] == std::make_pair(std::string("discard_left"), std::string("NoticeDiscard"))); + { + INFO("a kept text first"); + CHECK(notice.text == said("NoticeRecoverable", "[WHEN]", when)); + CHECK(notice.buttons[0].first == "compare_kept"); + CHECK(notice.buttons[1].first == "restore"); + CHECK(notice.buttons[2] == std::make_pair(std::string("discard_left"), std::string("NoticeDiscard"))); + } a.assetId = LLUUID::generateNewID(); - ensure("stale where saved since", Orphans::noticeFor(&a, words).text == said("NoticeRecoverableStale", "[WHEN]", when)); + UNSCOPED_INFO("stale where saved since"); + CHECK(Orphans::noticeFor(&a, words).text == said("NoticeRecoverableStale", "[WHEN]", when)); a.recoverable.reset(); notice = Orphans::noticeFor(&a, words); - ensure("away", notice.text == said("NoticeAway") && notice.buttons[0].first == "copy" && notice.buttons[1].first == "export"); + CHECK(notice.text == said("NoticeAway")); + CHECK(notice.buttons[0].first == "copy"); + CHECK(notice.buttons[1].first == "export"); a.orphan->kind = Orphan::Offline; notice = Orphans::noticeFor(&a, words); - ensure("offline: a copy on disk alone", notice.buttons[0].first == "export" && notice.buttons[1].first.empty()); + { + INFO("offline: a copy on disk alone"); + CHECK(notice.buttons[0].first == "export"); + CHECK(notice.buttons[1].first.empty()); + } a.orphan->kind = Orphan::Unloaded; a.loadError = "no such asset"; notice = Orphans::noticeFor(&a, words); - ensure("unloaded: why, and to try again", - notice.text.find("no such asset") != std::string::npos && notice.buttons[0].first == "retry_load"); + { + INFO("unloaded: why, and to try again"); + CHECK(notice.text.find("no such asset") != std::string::npos); + CHECK(notice.buttons[0].first == "retry_load"); + } a.orphan->kind = Orphan::Trashed; notice = Orphans::noticeFor(&a, words); - ensure("in the Trash: said, nothing to do", notice.text == said("NoticeTrashed") && notice.buttons[0].first.empty()); + { + INFO("in the Trash: said, nothing to do"); + CHECK(notice.text == said("NoticeTrashed")); + CHECK(notice.buttons[0].first.empty()); + } a.orphan->kind = Orphan::FileGone; a.file = "/somewhere/a.lsl"; notice = Orphans::noticeFor(&a, words); - ensure("a file gone: saved again", notice.text.find("/somewhere/a.lsl") != std::string::npos && notice.buttons[0].first == "save"); + { + INFO("a file gone: saved again"); + CHECK(notice.text.find("/somewhere/a.lsl") != std::string::npos); + CHECK(notice.buttons[0].first == "save"); + } Doc& i = tab("i", true); i.orphan->kind = Orphan::Removed; - ensure("gone from the inventory, said so", Orphans::noticeFor(&i, words).text == said("NoticeRemovedInventory")); + UNSCOPED_INFO("gone from the inventory, said so"); + CHECK(Orphans::noticeFor(&i, words).text == said("NoticeRemovedInventory")); i.orphan->noticeDismissed = true; - ensure("hidden: nothing", Orphans::noticeFor(&i, words).text.empty()); + UNSCOPED_INFO("hidden: nothing"); + CHECK(Orphans::noticeFor(&i, words).text.empty()); } - template<> template<> - void alscriptstudioorphans_object::test<6>() + TEST_CASE_METHOD(alscriptstudioorphans_data, "alscriptstudioorphans: the notice's actions act on the tab in front", "[alscriptstudioorphans]") { - set_test_name("the notice's actions for the tab in front: hide, restore, discard, try again, a copy, a file, a save"); Orphans& unit = make(); Doc& a = tab("a"); services().front = 0; a.recoverable = recovery->leftFor(a, "restored text"); unit.noticeAction("restore"); - ensure("restored, the source in front", studio.did == Names{ "source a", "carried a: restored text" } && !a.recoverable && - services().reports.back().text == said("RecoveryRestored", "[NAME]", "a")); + { + INFO("restored, the source in front"); + CHECK(studio.did == Names{ "source a", "carried a: restored text" }); + CHECK_FALSE(a.recoverable); + CHECK(services().reports.back().text == said("RecoveryRestored", "[NAME]", "a")); + } a.recoverable = ALRecoveryEntry(); unit.noticeAction("discard_left"); - ensure("discarded", studio.did.back() == "discard" && !a.recoverable); + CHECK(studio.did.back() == "discard"); + CHECK_FALSE(a.recoverable); a.recoverable = ALRecoveryEntry(); unit.noticeAction("close"); - ensure("hidden, the kept text let go of here", a.orphan->noticeDismissed && !a.recoverable); + { + INFO("hidden, the kept text let go of here"); + CHECK(a.orphan->noticeDismissed); + CHECK_FALSE(a.recoverable); + } unit.noticeAction("copy"); unit.noticeAction("export"); unit.noticeAction("save"); - ensure("a copy, a file, a save", - studio.did == Names{ "source a", "carried a: restored text", "source a", "discard", "copy a", "save a" } && - files->picked == Names{ "a" }); + CHECK(studio.did == Names{ "source a", "carried a: restored text", "source a", "discard", "copy a", "save a" }); + CHECK(files->picked == Names{ "a" }); a.orphan->reattachTries = 3; unit.noticeAction("retry_load"); - ensure("not detached: nothing to try", studio.loads.empty()); + UNSCOPED_INFO("not detached: nothing to try"); + CHECK(studio.loads.empty()); a.orphan->detached = true; unit.noticeAction("retry_load"); - ensure("tried again, from the first try", studio.loads.size() == 1 && a.orphan->reattachTries == 0); + { + INFO("tried again, from the first try"); + CHECK(studio.loads.size() == 1); + CHECK(a.orphan->reattachTries == 0); + } } - template<> template<> - void alscriptstudioorphans_object::test<7>() + TEST_CASE_METHOD(alscriptstudioorphans_data, "alscriptstudioorphans: the bar shows words and buttons and hides with nothing to say", "[alscriptstudioorphans]") { - set_test_name("the bar: shown with its words and buttons, as wide as their labels; hidden with nothing to say; its buttons the window's"); Orphans& unit = make(); ALScriptNoticeBar* bar = window.find("notice"); - ensure("built from the skin", bar != nullptr); + UNSCOPED_INFO("built from the skin"); + REQUIRE(bar != nullptr); studio.bar = bar; Doc& a = tab("a"); services().front = 0; a.orphan->kind = Orphan::Away; unit.refreshNotice(); LLView* holder = bar->getParent(); - ensure("shown", holder->getVisible()); - ensure_equals("its words", bar->getChild("notice_text")->getText(), said("NoticeAway")); + CHECK(holder->getVisible()); + CHECK(bar->getChild("notice_text")->getText() == said("NoticeAway")); LLButton* first = bar->getChild("notice_first"); LLButton* second = bar->getChild("notice_second"); - ensure("its buttons, labelled", first->getVisible() && second->getVisible() && first->getLabelSelected() == said("NoticeCopy") && - first->getToolTip() == said("NoticeCopyTip")); - ensure("the first left of the second", first->getRect().mRight < second->getRect().mLeft); + { + INFO("its buttons, labelled"); + CHECK(first->getVisible()); + CHECK(second->getVisible()); + CHECK(first->getLabelSelected() == said("NoticeCopy")); + CHECK(first->getToolTip() == said("NoticeCopyTip")); + } + UNSCOPED_INFO("the first left of the second"); + CHECK(first->getRect().mRight < second->getRect().mLeft); first->onCommit(); second->onCommit(); bar->getChild("notice_close")->onCommit(); - ensure("the window told", window.pane().pressed == Names{ "copy", "export", "close" }); + UNSCOPED_INFO("the window told"); + CHECK(window.pane().pressed == Names{ "copy", "export", "close" }); a.orphan->kind = Orphan::Offline; unit.refreshNotice(); - ensure("one button", first->getVisible() && !second->getVisible() && bar->action(0) == "export"); + { + INFO("one button"); + CHECK(first->getVisible()); + CHECK_FALSE(second->getVisible()); + CHECK(bar->action(0) == "export"); + } a.orphan->kind = Orphan::None; unit.refreshNotice(); - ensure("hidden", !holder->getVisible()); + CHECK_FALSE(holder->getVisible()); } - template<> template<> - void alscriptstudioorphans_object::test<8>() + TEST_CASE_METHOD(alscriptstudioorphans_data, "alscriptstudioorphans: a compiled half the source could not make is compared or kept or taken", "[alscriptstudioorphans]") { - set_test_name("a compiled half the source could not have made: said after a kept text and a tab gone, kept as source or taken as it"); Orphans& unit = make(); const al_studio_test::FakeServices& words = services(); Doc& a = tab("a"); services().front = 0; a.compiledDiffers = std::string("default { touch_start(integer n) { llDie(); } }"); ALScriptNoticeBar::Notice notice = Orphans::noticeFor(&a, words); - ensure("said, with its ways", notice.text == said("NoticeCompiledDiffers") && notice.buttons[0].first == "compare_compiled" && - notice.buttons[1].first == "keep_source" && notice.buttons[2].first == "take_compiled"); + { + INFO("said, with its ways"); + CHECK(notice.text == said("NoticeCompiledDiffers")); + CHECK(notice.buttons[0].first == "compare_compiled"); + CHECK(notice.buttons[1].first == "keep_source"); + CHECK(notice.buttons[2].first == "take_compiled"); + } a.uploaded.text = std::make_shared("default { }"); unit.noticeAction("compare_compiled"); - ensure_equals("compared: as saved, beside what the source makes", studio.did.back(), + UNSCOPED_INFO("compared: as saved, beside what the source makes"); + CHECK(studio.did.back() == "compare a: default { touch_start(integer n) { llDie(); } } | default { } (" + said("CompareCompiled") + " | " + said("CompareMade") + ")"); - ensure("the notice stays, to act on after looking", a.compiledDiffers.has_value()); + UNSCOPED_INFO("the notice stays, to act on after looking"); + CHECK(a.compiledDiffers.has_value()); studio.did.clear(); a.orphan->kind = Orphan::Away; - ensure("a tab gone first", Orphans::noticeFor(&a, words).text == said("NoticeAway")); + UNSCOPED_INFO("a tab gone first"); + CHECK(Orphans::noticeFor(&a, words).text == said("NoticeAway")); a.orphan->kind = Orphan::None; unit.noticeAction("take_compiled"); - ensure("the source back in front, then taken as the source", - studio.did == Names{ "source a", "carried a: default { touch_start(integer n) { llDie(); } }" } && !a.compiledDiffers); - ensure("and said", services().reports.back().text == said("CompiledTaken", "[NAME]", "a")); + { + INFO("the source back in front, then taken as the source"); + CHECK(studio.did == Names{ "source a", "carried a: default { touch_start(integer n) { llDie(); } }" }); + CHECK_FALSE(a.compiledDiffers); + } + UNSCOPED_INFO("and said"); + CHECK(services().reports.back().text == said("CompiledTaken", "[NAME]", "a")); a.compiledDiffers = std::string("x"); unit.noticeAction("keep_source"); - ensure("kept: let go of, the next save replacing it", !a.compiledDiffers && studio.did.back() == "source a"); - ensure("nothing more to say", Orphans::noticeFor(&a, words).text.empty()); + { + INFO("kept: let go of, the next save replacing it"); + CHECK_FALSE(a.compiledDiffers); + CHECK(studio.did.back() == "source a"); + } + UNSCOPED_INFO("nothing more to say"); + CHECK(Orphans::noticeFor(&a, words).text.empty()); } - template<> template<> - void alscriptstudioorphans_object::test<9>() + TEST_CASE_METHOD(alscriptstudioorphans_data, "alscriptstudioorphans: a kept text compares with the tab and restores with the source in front", "[alscriptstudioorphans]") { - set_test_name("a kept text compared with what the tab holds now, the notice staying; restoring puts the source back in front"); Orphans& unit = make(); Doc& a = tab("a"); services().front = 0; // As a tab is offered it: the entry's listing, not yet its text. const ALRecoveryEntry kept = recovery->leftFor(a, "default { state_entry() { } }"); a.recoverable = kept; - ensure("offered as listed", !kept.whole); + UNSCOPED_INFO("offered as listed"); + CHECK_FALSE(kept.whole); unit.noticeAction("compare_kept"); - ensure_equals("now beside kept", studio.did.back(), + UNSCOPED_INFO("now beside kept"); + CHECK(studio.did.back() == "compare a: default {} | default { state_entry() { } } (" + said("CompareNow") + " | " + said("CompareKept", "[WHEN]", kept.whenSaid()) + ")"); - ensure("still offered", a.recoverable.has_value()); + UNSCOPED_INFO("still offered"); + CHECK(a.recoverable.has_value()); unit.noticeAction("restore"); - ensure("the source in front first", - studio.did[studio.did.size() - 2] == "source a" && studio.did.back() == "carried a: default { state_entry() { } }"); + { + INFO("the source in front first"); + CHECK(studio.did[studio.did.size() - 2] == "source a"); + CHECK(studio.did.back() == "carried a: default { state_entry() { } }"); + } } - template<> template<> - void alscriptstudioorphans_object::test<10>() + TEST_CASE_METHOD(alscriptstudioorphans_data, "alscriptstudioorphans: the last word's offer shows after the tab's state and is taken or hidden", "[alscriptstudioorphans]") { - set_test_name("what the last word offered: said after what the tab is, in the notice's words, a conflict's only while there is another text; " - "taken up as Output would, or hidden"); Orphans& unit = make(); const al_studio_test::FakeServices& words = services(); Doc& a = tab("a"); services().front = 0; a.offer = Doc::Offer{ "Saving a failed.", { "retry", "copy", "export" } }; ALScriptNoticeBar::Notice notice = Orphans::noticeFor(&a, words); - ensure("said with its buttons", notice.text == "Saving a failed." && - notice.buttons[0] == std::make_pair(std::string("retry"), std::string("NoticeRetrySave")) && - notice.buttons[1].first == "copy" && notice.buttons[2].first == "export"); + { + INFO("said with its buttons"); + CHECK(notice.text == "Saving a failed."); + CHECK(notice.buttons[0] == std::make_pair(std::string("retry"), std::string("NoticeRetrySave"))); + CHECK(notice.buttons[1].first == "copy"); + CHECK(notice.buttons[2].first == "export"); + } a.orphan->kind = Orphan::Away; - ensure("what the tab is first", Orphans::noticeFor(&a, words).text == said("NoticeAway")); + UNSCOPED_INFO("what the tab is first"); + CHECK(Orphans::noticeFor(&a, words).text == said("NoticeAway")); a.orphan->noticeDismissed = true; - ensure("then the offer, once that is hidden", Orphans::noticeFor(&a, words).text == "Saving a failed."); + UNSCOPED_INFO("then the offer, once that is hidden"); + CHECK(Orphans::noticeFor(&a, words).text == "Saving a failed."); a.orphan->kind = Orphan::None; a.orphan->noticeDismissed = false; unit.noticeAction("retry"); - ensure("taken up, and let go of", studio.did.back() == "offer a: retry" && !a.offer); + { + INFO("taken up, and let go of"); + CHECK(studio.did.back() == "offer a: retry"); + CHECK_FALSE(a.offer); + } a.offer = Doc::Offer{ "Saved elsewhere.", { "take_saved", "keep_saved", "merge_saved", "compare_saved" } }; - ensure("a conflict with nothing to take: not said", Orphans::noticeFor(&a, words).text.empty()); + UNSCOPED_INFO("a conflict with nothing to take: not said"); + CHECK(Orphans::noticeFor(&a, words).text.empty()); a.savedThere = std::string("theirs"); notice = Orphans::noticeFor(&a, words); - ensure("with it: said, all four", notice.text == "Saved elsewhere." && notice.buttons[0].second == "NoticeTakeSaved" && - notice.buttons[2].second == "NoticeMerge" && notice.buttons[3].second == "NoticeCompare"); + { + INFO("with it: said, all four"); + CHECK(notice.text == "Saved elsewhere."); + CHECK(notice.buttons[0].second == "NoticeTakeSaved"); + CHECK(notice.buttons[2].second == "NoticeMerge"); + CHECK(notice.buttons[3].second == "NoticeCompare"); + } unit.noticeAction("close"); - ensure("hidden: let go of, and nothing else", !a.offer && !a.orphan->noticeDismissed && studio.did.back() == "offer a: retry"); + { + INFO("hidden: let go of, and nothing else"); + CHECK_FALSE(a.offer); + CHECK_FALSE(a.orphan->noticeDismissed); + CHECK(studio.did.back() == "offer a: retry"); + } a.offer = Doc::Offer{ "Held.", { "save_anyway" } }; a.compiledDiffers = std::string("x"); - ensure("said before a compiled half", Orphans::noticeFor(&a, words).text == "Held."); - ensure("a save's, answered by a save; a conflict's not", - a.offer->bySave() && !Doc::Offer{ "x", { "take_external", "keep_here" } }.bySave()); + UNSCOPED_INFO("said before a compiled half"); + CHECK(Orphans::noticeFor(&a, words).text == "Held."); + { + INFO("a save's, answered by a save; a conflict's not"); + CHECK(a.offer->bySave()); + CHECK_FALSE(Doc::Offer{ "x", { "take_external", "keep_here" } }.bySave()); + } a.orphan->kind = Orphan::Removed; a.offer = Doc::Offer{ "Kept.", { "copy", "export" } }; unit.noticeAction("close"); - ensure("what a tab gone says of itself, hidden with it", a.orphan->noticeDismissed && !a.offer); + { + INFO("what a tab gone says of itself, hidden with it"); + CHECK(a.orphan->noticeDismissed); + CHECK_FALSE(a.offer); + } a.orphan->noticeDismissed = false; a.offer = Doc::Offer{ "Saving a failed.", { "retry", "copy", "export" } }; unit.noticeAction("close"); - ensure("more than that: said next", a.offer && Orphans::noticeFor(&a, words).text == "Saving a failed."); + { + INFO("more than that: said next"); + CHECK(a.offer); + CHECK(Orphans::noticeFor(&a, words).text == "Saving a failed."); + } } - template<> template<> - void alscriptstudioorphans_object::test<11>() + TEST_CASE_METHOD(alscriptstudioorphans_data, "alscriptstudioorphans: no skin string begins with a quotation mark so code adds vim's quotes", "[alscriptstudioorphans]") { - set_test_name("no string of the skin's begins with a quotation mark, which the skin reads as quoting it: vim's quotes are put in by the code"); make(); - ensure_equals("a name in quotes", said("RenameBadName", "[NAME]", "9lives"), std::string("Not a valid name: \"9lives\".")); + UNSCOPED_INFO("a name in quotes"); + CHECK(said("RenameBadName", "[NAME]", "9lives") == std::string("Not a valid name: \"9lives\".")); LLStringUtil::format_map_t written; written["[QUOTED]"] = "\"a.lsl\""; written["[LINES]"] = "4"; written["[BYTES]"] = "20"; - ensure_equals("vim's written", services().words("VimWritten", written), std::string("\"a.lsl\" 4L, 20B written")); + UNSCOPED_INFO("vim's written"); + CHECK(services().words("VimWritten", written) == std::string("\"a.lsl\" 4L, 20B written")); const std::string vimrc = services().words("VimrcNewFile"); - ensure("the vimrc's words whole", vimrc.rfind("Script Studio's vimrc.", 0) == 0 && std::count(vimrc.begin(), vimrc.end(), '\n') == 3); + { + INFO("the vimrc's words whole"); + CHECK(vimrc.rfind("Script Studio's vimrc.", 0) == 0); + CHECK(std::count(vimrc.begin(), vimrc.end(), '\n') == 3); + } } - template<> template<> - void alscriptstudioorphans_object::test<12>() + TEST_CASE_METHOD(alscriptstudioorphans_data, "alscriptstudioorphans: a tab's standing changes bring the notice back and space out retries", "[alscriptstudioorphans]") { - set_test_name("a tab's standing as the window and a save change it: the notice back each time, a kept text detached where it is an item's, " - "a failed load tried later and further apart"); make(); Doc& a = tab("a"); a.orphan->noticeDismissed = true; Orphans::become(a, Doc::Orphan::Removed); - ensure("removed, and said", a.orphan->kind == Doc::Orphan::Removed && !a.orphan->noticeDismissed && !a.orphan->detached); + { + INFO("removed, and said"); + CHECK(a.orphan->kind == Doc::Orphan::Removed); + CHECK_FALSE(a.orphan->noticeDismissed); + CHECK_FALSE(a.orphan->detached); + } a.orphan->noticeDismissed = true; Orphans::showNotice(a); - ensure("the notice back", !a.orphan->noticeDismissed); + UNSCOPED_INFO("the notice back"); + CHECK_FALSE(a.orphan->noticeDismissed); Orphans::detach(a, Doc::Orphan::Unloaded); - ensure("an item's kept text detached", a.orphan->detached && a.orphan->kind == Doc::Orphan::Unloaded); + { + INFO("an item's kept text detached"); + CHECK(a.orphan->detached); + CHECK(a.orphan->kind == Doc::Orphan::Unloaded); + } Doc& f = tab("f"); f.file = "/nowhere/f.lsl"; Orphans::detach(f, Doc::Orphan::FileGone); - ensure("a file's not: nothing is loaded under it", !f.orphan->detached && f.orphan->kind == Doc::Orphan::FileGone); + { + INFO("a file's not: nothing is loaded under it"); + CHECK_FALSE(f.orphan->detached); + CHECK(f.orphan->kind == Doc::Orphan::FileGone); + } const F64 now = LLTimer::getTotalSeconds(); Orphans::loadFailed(a); const F64 first = a.orphan->nextReattach - now; Orphans::loadFailed(a); const F64 second = a.orphan->nextReattach - now; - ensure("tried later, further apart each time", a.orphan->reattachTries == 2 && first > 0.0 && second > first); + { + INFO("tried later, further apart each time"); + CHECK(a.orphan->reattachTries == 2); + CHECK(first > 0.0); + CHECK(second > first); + } Orphans::loadWentThrough(a); - ensure("from the first try once one goes through", a.orphan->reattachTries == 0); + UNSCOPED_INFO("from the first try once one goes through"); + CHECK(a.orphan->reattachTries == 0); } - template<> template<> - void alscriptstudioorphans_object::test<13>() + TEST_CASE_METHOD(alscriptstudioorphans_data, "alscriptstudioorphans: a history save offered back is restored as carried text or let go of", "[alscriptstudioorphans]") { - set_test_name("a save of the history offered back: restored as carried text, the comparison ended, said; or let go of with the notice"); Orphans& unit = make(); Doc& a = tab("a"); ALSavedText shown; @@ -548,18 +711,28 @@ namespace tut shown.when = LLDate(1.8e9); a.historyShown = shown; unit.noticeAction("restore_saved"); - ensure("the source back in front, then taken", studio.did == Names{ "source a", "carried a: default { }" } && !a.historyShown); + { + INFO("the source back in front, then taken"); + CHECK(studio.did == Names{ "source a", "carried a: default { }" }); + CHECK_FALSE(a.historyShown); + } LLStringUtil::format_map_t args; args["[NAME]"] = "a"; args["[WHEN]"] = ALRecoveryEntry::sayWhen(shown.when); - ensure_equals("and said", services().reports.back().text, services().words("HistoryRestored", args)); + UNSCOPED_INFO("and said"); + CHECK(services().reports.back().text == services().words("HistoryRestored", args)); studio.did.clear(); a.historyShown = shown; a.modifiable = false; unit.noticeAction("restore_saved"); - ensure("not into a tab that may not be changed", studio.did.empty() && a.historyShown); + { + INFO("not into a tab that may not be changed"); + CHECK(studio.did.empty()); + CHECK(a.historyShown); + } unit.noticeAction("close"); - ensure("let go of with the notice", !a.historyShown); + UNSCOPED_INFO("let go of with the notice"); + CHECK_FALSE(a.historyShown); } } diff --git a/indra/newview/tests/alscriptstudiorecovery_test.cpp b/indra/newview/tests/alscriptstudiorecovery_test.cpp index 47d6456630..93f723e8dc 100644 --- a/indra/newview/tests/alscriptstudiorecovery_test.cpp +++ b/indra/newview/tests/alscriptstudiorecovery_test.cpp @@ -33,7 +33,7 @@ #include "fsyspath.h" #include "lltimer.h" -#include "../test/lltut.h" +#include #include #include @@ -106,7 +106,7 @@ namespace }; } -namespace tut +namespace { struct alscriptstudiorecovery_data { @@ -115,6 +115,10 @@ namespace tut std::string folder; std::unique_ptr store; + // Every store a case makes, the fixture's or its own, writes on the + // process's writer. + alscriptstudiorecovery_data() { newview_test::useRecoveryWriter(); } + ~alscriptstudiorecovery_data() { ALRecovery::useStore(nullptr); @@ -133,7 +137,7 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } recovery = std::make_unique(services(), studio(), studio()); studio().recovery = recovery.get(); @@ -179,7 +183,7 @@ namespace tut } // An entry another session left for a tab's script. - Entry leftFor(const Doc& doc, const std::string& text, const std::string& session = "old-session") + Entry require_left_for(const Doc& doc, const std::string& text, const std::string& session = "old-session") { ALRecoveryStore other(folder, session); Entry entry = ALScriptStudioRecovery::entryOf(doc); @@ -189,7 +193,8 @@ namespace tut other.write(entry); // What another session left, as this one's store finds it. const std::optional left = store->leftFor(entry.key); - ensure("left by the other session", left.has_value()); + UNSCOPED_INFO("left by the other session"); + REQUIRE(left.has_value()); return *left; } @@ -213,68 +218,102 @@ namespace tut return count; } }; - typedef test_group alscriptstudiorecovery_group; - typedef alscriptstudiorecovery_group::object alscriptstudiorecovery_object; - tut::alscriptstudiorecovery_group alscriptstudiorecovery_instance("alscriptstudiorecovery"); - template<> template<> - void alscriptstudiorecovery_object::test<1>() + TEST_CASE_METHOD(alscriptstudiorecovery_data, "alscriptstudiorecovery: restoreHistory brings back an entry's text and steps and caret", "[alscriptstudiorecovery]") { - set_test_name("an entry is the tab as it stands, and put back over a tab of its script holding nothing of its own, brings its text, its history and its caret"); ALScriptStudioRecovery& r = make(); Doc& a = tab("a", "default {}\n"); type(a, "// one"); type(a, " two"); const Entry entry = ALScriptStudioRecovery::entryOf(a); - ensure("the text", entry.text == "default {}\n// one two" && entry.key == a.recoveryKey); - ensure("where it came from", entry.object == a.ref.object && entry.item == a.ref.item && entry.name == "a" && !entry.notecard); - ensure("the caret", entry.caretLine == 1 && entry.caretColumn == 10); - ensure("and the steps, as the journal writes them", entry.historyOf().isMap() && !entry.historyWritten.empty()); + CHECK(entry.text == "default {}\n// one two"); + CHECK(entry.key == a.recoveryKey); + { + INFO("where it came from"); + CHECK(entry.object == a.ref.object); + CHECK(entry.item == a.ref.item); + CHECK(entry.name == "a"); + CHECK_FALSE(entry.notecard); + } + CHECK(entry.caretLine == 1); + CHECK(entry.caretColumn == 10); + { + INFO("and the steps, as the journal writes them"); + CHECK(entry.historyOf().isMap()); + REQUIRE_FALSE(entry.historyWritten.empty()); + } Doc& b = tab("b", "default {}\n", a.ref); - ensure("put back", r.restoreHistory(b, entry)); - ensure("its text", b.editor->text() == entry.text && b.editor->isDirty()); - ensure("its caret", b.editor->caret().line == 1 && b.editor->caret().column == 10); + CHECK(r.restoreHistory(b, entry)); + CHECK(b.editor->text() == entry.text); + CHECK(b.editor->isDirty()); + CHECK(b.editor->caret().line == 1); + CHECK(b.editor->caret().column == 10); b.editor->undo(); - ensure("and the steps that led to it, to take back", b.editor->text() != entry.text && b.editor->text().starts_with("default {}\n")); - ensure("the tabs told", studio().tabs == 1); + { + INFO("and the steps that led to it, to take back"); + CHECK(b.editor->text() != entry.text); + CHECK(b.editor->text().starts_with("default {}\n")); + } + UNSCOPED_INFO("the tabs told"); + CHECK(studio().tabs == 1); type(b, "x"); - ensure("not over a tab holding something of its own", !r.restoreHistory(b, entry)); + UNSCOPED_INFO("not over a tab holding something of its own"); + CHECK_FALSE(r.restoreHistory(b, entry)); Doc& c = tab("c", "something else\n", a.ref); Entry wrong = entry; wrong.text = "not where the steps lead\n"; - ensure("nor where the history is not of its text", !r.restoreHistory(c, wrong) && c.editor->text() == "something else\n"); + { + INFO("nor where the history is not of its text"); + CHECK_FALSE(r.restoreHistory(c, wrong)); + CHECK(c.editor->text() == "something else\n"); + } } - template<> template<> - void alscriptstudiorecovery_object::test<2>() + TEST_CASE_METHOD(alscriptstudiorecovery_data, "alscriptstudiorecovery: keep holds unsaved text and forgets it once the tab is clean", "[alscriptstudiorecovery]") { - set_test_name("kept while unsaved and forgotten once clean; what a tab took up is its own to keep from then on"); ALScriptStudioRecovery& r = make(); Doc& doc = tab("a", "x\n"); - ensure("clean: nothing kept", r.keep(doc) && keptFor(doc.recoveryKey) == 0); + { + INFO("clean: nothing kept"); + CHECK(r.keep(doc)); + CHECK(keptFor(doc.recoveryKey) == 0); + } type(doc, "y"); - ensure("kept", r.keep(doc) && keptFor(doc.recoveryKey) == 1); + CHECK(r.keep(doc)); + CHECK(keptFor(doc.recoveryKey) == 1); doc.editor->resetDirty(); - ensure("clean again: forgotten", r.keep(doc) && keptFor(doc.recoveryKey) == 0); + { + INFO("clean again: forgotten"); + CHECK(r.keep(doc)); + CHECK(keptFor(doc.recoveryKey) == 0); + } - doc.recovering = leftFor(doc, "an old session's"); - ensure("the old session's there", keptFor(doc.recoveryKey, "old-session") == 1); + doc.recovering = require_left_for(doc, "an old session's"); + UNSCOPED_INFO("the old session's there"); + CHECK(keptFor(doc.recoveryKey, "old-session") == 1); type(doc, "z"); r.keep(doc); - ensure("taken up, it is this session's alone", keptFor(doc.recoveryKey, "old-session") == 0 && keptFor(doc.recoveryKey) == 1 && !doc.recovering); + { + INFO("taken up, it is this session's alone"); + CHECK(keptFor(doc.recoveryKey, "old-session") == 0); + CHECK(keptFor(doc.recoveryKey) == 1); + REQUIRE_FALSE(doc.recovering); + } Doc& loading = tab("b", "q\n"); type(loading, "w"); loading.loaded = false; - ensure("nothing kept of a tab still loading", r.keep(loading) && keptFor(loading.recoveryKey) == 0); + { + INFO("nothing kept of a tab still loading"); + CHECK(r.keep(loading)); + CHECK(keptFor(loading.recoveryKey) == 0); + } } - template<> template<> - void alscriptstudiorecovery_object::test<3>() + TEST_CASE_METHOD(alscriptstudiorecovery_data, "alscriptstudiorecovery: a failing write is reported once whether now or from the store's thread", "[alscriptstudiorecovery]") { - set_test_name("a write that fails is said once, not at every pause in typing, whether now or on the store's thread; one that works again may say it again"); ALScriptStudioRecovery& r = make(); Doc& doc = tab("a", "x\n"); const std::string blocked = folder + "/blocked"; @@ -285,60 +324,92 @@ namespace tut ALRecoveryStore nowhere(blocked, "this-session"); ALRecovery::useStore(&nowhere); type(doc, "y"); - ensure("not kept", !r.keep(doc)); - ensure("said", services().reports.size() == 1 && services().reports[0].failure && services().reports[0].doc == "a" && - services().reports[0].text.find("a") != std::string::npos); + CHECK_FALSE(r.keep(doc)); + REQUIRE(services().reports.size() == 1); + CHECK(services().reports[0].failure); + CHECK(services().reports[0].doc == "a"); + CHECK(services().reports[0].text.find("a") != std::string::npos); type(doc, "z"); - ensure("not said again", !r.keep(doc) && services().reports.size() == 1); + { + INFO("not said again"); + CHECK_FALSE(r.keep(doc)); + REQUIRE(services().reports.size() == 1); + } ALRecovery::useStore(store.get()); - ensure("kept, once it can be", r.keep(doc) && !doc.recovery->failed); + { + INFO("kept, once it can be"); + REQUIRE(r.keep(doc)); + CHECK_FALSE(doc.recovery->failed); + } ALRecovery::useStore(&nowhere); type(doc, "w"); r.keepSoon(doc); nowhere.flush(); r.keepSoon(doc); - ensure("from the store's thread, said once", services().reports.size() == 2 && doc.recovery->failed); + { + INFO("from the store's thread, said once"); + REQUIRE(services().reports.size() == 2); + CHECK(doc.recovery->failed); + } nowhere.flush(); r.keepSoon(doc); nowhere.flush(); r.keepSoon(doc); - ensure("and not again", services().reports.size() == 2); + UNSCOPED_INFO("and not again"); + REQUIRE(services().reports.size() == 2); ALRecovery::useStore(store.get()); } - template<> template<> - void alscriptstudiorecovery_object::test<4>() + TEST_CASE_METHOD(alscriptstudiorecovery_data, "alscriptstudiorecovery: takeUp waits for the tab to load and goes in as one step to undo", "[alscriptstudiorecovery]") { - set_test_name("a kept text taken up waits for its tab to load, then goes in as one step to undo; over a tab nothing can be saved from, the tab holds it on its own"); ALScriptStudioRecovery& r = make(); Doc& doc = tab("a", "old\n"); - const Entry kept = leftFor(doc, "kept\n"); + const Entry kept = require_left_for(doc, "kept\n"); doc.loaded = false; r.takeUp(doc, kept); - ensure("waiting", doc.carriedText == std::optional("kept\n") && doc.recovering && doc.editor->text() == "old\n"); + { + INFO("waiting"); + CHECK(doc.carriedText == std::optional("kept\n")); + REQUIRE(doc.recovering); + CHECK(doc.editor->text() == "old\n"); + } doc.loaded = true; doc.carriedText.reset(); doc.recovering.reset(); r.takeUp(doc, kept); - ensure("in", doc.editor->text() == "kept\n" && doc.editor->isDirty() && !doc.carriedText); - ensure("kept by this session, the old entry let go of", keptFor(doc.recoveryKey) == 1 && keptFor(doc.recoveryKey, "old-session") == 0); - ensure("the notice told", studio().notices == 1); + CHECK(doc.editor->text() == "kept\n"); + CHECK(doc.editor->isDirty()); + REQUIRE_FALSE(doc.carriedText); + { + INFO("kept by this session, the old entry let go of"); + CHECK(keptFor(doc.recoveryKey) == 1); + CHECK(keptFor(doc.recoveryKey, "old-session") == 0); + } + UNSCOPED_INFO("the notice told"); + CHECK(studio().notices == 1); doc.editor->undo(); - ensure("one step to undo", doc.editor->text() == "old\n"); + UNSCOPED_INFO("one step to undo"); + CHECK(doc.editor->text() == "old\n"); Doc& locked = tab("b", "locked\n"); locked.modifiable = false; - r.takeUp(locked, leftFor(locked, "kept too\n")); - ensure("held on its own", studio().became.size() == 1 && studio().became[0] == std::make_pair(std::string("b"), Doc::Orphan::Locked)); - ensure("and said, with what can be done with it", services().reports.size() == 1 && services().reports[0].actions.size() == 2); + r.takeUp(locked, require_left_for(locked, "kept too\n")); + { + INFO("held on its own"); + REQUIRE(studio().became.size() == 1); + CHECK(studio().became[0] == std::make_pair(std::string("b"), Doc::Orphan::Locked)); + } + { + INFO("and said, with what can be done with it"); + REQUIRE(services().reports.size() == 1); + REQUIRE(services().reports[0].actions.size() == 2); + } } - template<> template<> - void alscriptstudiorecovery_object::test<5>() + TEST_CASE_METHOD(alscriptstudiorecovery_data, "alscriptstudiorecovery: recover takes an entry to where it belongs or to a tab of its own", "[alscriptstudiorecovery]") { - set_test_name("recovered where it belongs: another window that has it; its file, or a tab of its own where the file is gone; its script where it can be had, or a tab of its own"); ALScriptStudioRecovery& r = make(); Entry entry; entry.name = "held"; @@ -348,7 +419,11 @@ namespace tut studio().elsewhere = true; r.recover(entry); - ensure("in the window that has it", studio().tookElsewhere.size() == 1 && studio().orphans.empty()); + { + INFO("in the window that has it"); + REQUIRE(studio().tookElsewhere.size() == 1); + REQUIRE(studio().orphans.empty()); + } studio().elsewhere = false; Entry file = entry; @@ -357,7 +432,11 @@ namespace tut file.file = folder + "/gone.lsl"; file.name = "gone"; r.recover(file); - ensure("a file gone, a tab of its own", studio().orphans.size() == 1 && studio().orphans[0].second == Doc::Orphan::FileGone); + { + INFO("a file gone, a tab of its own"); + REQUIRE(studio().orphans.size() == 1); + CHECK(studio().orphans[0].second == Doc::Orphan::FileGone); + } { llofstream there(folder + "/here.lsl"); there << "on disk\n"; @@ -370,15 +449,27 @@ namespace tut return &doc; }; r.recover(file); - ensure("a file there, opened and taken up", studio().activated.size() == 1 && services().findDoc("disk:" + file.file)->carriedText); + { + INFO("a file there, opened and taken up"); + REQUIRE(studio().activated.size() == 1); + REQUIRE(services().findDoc("disk:" + file.file)->carriedText); + } studio().inHand = false; r.recover(entry); - ensure("an object out of sight, a tab of its own", studio().orphans.size() == 2 && studio().orphans[1].second == Doc::Orphan::Away); + { + INFO("an object out of sight, a tab of its own"); + REQUIRE(studio().orphans.size() == 2); + CHECK(studio().orphans[1].second == Doc::Orphan::Away); + } Entry inventory = entry; inventory.object.setNull(); r.recover(inventory); - ensure("an item gone from the inventory", studio().orphans.size() == 3 && studio().orphans[2].second == Doc::Orphan::Removed); + { + INFO("an item gone from the inventory"); + REQUIRE(studio().orphans.size() == 3); + CHECK(studio().orphans[2].second == Doc::Orphan::Removed); + } studio().inHand = true; services().whenOpened = [this](const ALScriptRef& ref, const std::string&) { @@ -386,34 +477,44 @@ namespace tut doc.loaded = false; }; r.recover(entry); - ensure("its script opened, and the text to go in once loaded", - services().opened.size() == 1 && services().findDoc(ALScriptRef(entry.object, entry.item))->carriedText == std::optional("kept\n")); + { + INFO("its script opened, and the text to go in once loaded"); + REQUIRE(services().opened.size() == 1); + CHECK(services().findDoc(ALScriptRef(entry.object, entry.item))->carriedText == std::optional("kept\n")); + } } - template<> template<> - void alscriptstudiorecovery_object::test<6>() + TEST_CASE_METHOD(alscriptstudiorecovery_data, "alscriptstudiorecovery: show offers other sessions' and discarded entries to pick or drop", "[alscriptstudiorecovery]") { - set_test_name("Recover Unsaved Changes offers what other sessions left and what was thrown away, not this session's own; picked, taken up; Shift-Return throws one away, and one thrown away already goes for good"); ALScriptStudioRecovery& r = make(); r.show(); - ensure("nothing to offer, said", services().statuses.size() == 1 && studio().candidates.empty()); + { + INFO("nothing to offer, said"); + REQUIRE(services().statuses.size() == 1); + REQUIRE(studio().candidates.empty()); + } Doc& mine = tab("mine", "a\n"); type(mine, "b"); r.keep(mine); Doc& theirs = tab("theirs", "c\n"); - const Entry left = leftFor(theirs, "left\n"); + const Entry left = require_left_for(theirs, "left\n"); Doc& gone = tab("gone", "d\n"); type(gone, "e"); - ensure("set aside", r.setAside(gone)); + CHECK(r.setAside(gone)); r.show(); - ensure("theirs and what was set aside", studio().candidates.size() == 2); + UNSCOPED_INFO("theirs and what was set aside"); + REQUIRE(studio().candidates.size() == 2); std::string theirs_value, gone_value; for (const ALQuickOpen::Candidate& one : studio().candidates) { (one.label == "theirs" ? theirs_value : gone_value) = one.value; } - ensure("by name", !theirs_value.empty() && !gone_value.empty()); + { + INFO("by name"); + REQUIRE_FALSE(theirs_value.empty()); + REQUIRE_FALSE(gone_value.empty()); + } studio().dropped(gone_value); bool still = false; @@ -421,7 +522,8 @@ namespace tut { still |= entry.name == "gone"; } - ensure("thrown away already: gone for good", !still); + UNSCOPED_INFO("thrown away already: gone for good"); + CHECK_FALSE(still); studio().dropped(theirs_value); bool discarded = false; @@ -429,43 +531,49 @@ namespace tut { discarded |= entry.name == "theirs" && entry.state == Entry::State::Discarded; } - ensure("thrown away", discarded); + UNSCOPED_INFO("thrown away"); + CHECK(discarded); theirs.carriedText.reset(); theirs.loaded = false; r.show(); studio().chosen(studio().candidates.front().value); - ensure("picked, taken up", theirs.carriedText.has_value()); + UNSCOPED_INFO("picked, taken up"); + REQUIRE(theirs.carriedText.has_value()); } - template<> template<> - void alscriptstudiorecovery_object::test<7>() + TEST_CASE_METHOD(alscriptstudiorecovery_data, "alscriptstudiorecovery: schedule writes soon after typing began and forgets a clean tab", "[alscriptstudiorecovery]") { - set_test_name("typing is written a moment after it began, whatever is typed meanwhile; a tab gone clean is forgotten at once"); ALScriptStudioRecovery& r = make(); Doc& doc = tab("a", "x\n"); type(doc, "y"); r.schedule(doc); const F64 due = doc.recovery->due; - ensure("due in a moment", due > LLTimer::getTotalSeconds()); + UNSCOPED_INFO("due in a moment"); + CHECK(due > LLTimer::getTotalSeconds()); type(doc, "z"); r.schedule(doc); - ensure("not put off by more typing", doc.recovery->due == due); + UNSCOPED_INFO("not put off by more typing"); + CHECK(doc.recovery->due == due); r.pump(); - ensure("not yet", keptFor(doc.recoveryKey) == 0); + UNSCOPED_INFO("not yet"); + CHECK(keptFor(doc.recoveryKey) == 0); doc.recovery->due = LLTimer::getTotalSeconds() - 1.0; r.pump(); store->flush(); - ensure("written", keptFor(doc.recoveryKey) == 1 && doc.recovery->due == 0.0); + { + INFO("written"); + REQUIRE(keptFor(doc.recoveryKey) == 1); + CHECK(doc.recovery->due == 0.0); + } doc.editor->resetDirty(); r.schedule(doc); - ensure("clean: forgotten at once", keptFor(doc.recoveryKey) == 0); + UNSCOPED_INFO("clean: forgotten at once"); + CHECK(keptFor(doc.recoveryKey) == 0); } - template<> template<> - void alscriptstudiorecovery_object::test<8>() + TEST_CASE_METHOD(alscriptstudiorecovery_data, "alscriptstudiorecovery: a closed window's unsaved tab is offered back when its script reopens", "[alscriptstudiorecovery]") { - set_test_name("a tab whose window went with it unsaved is offered back as its script opens again this session, and not while another tab holds it"); ALScriptStudioRecovery& r = make(); const ALScriptRef ref(LLUUID::generateNewID(), LLUUID::generateNewID()); Doc& was = tab("a", "x\n", ref); @@ -477,38 +585,58 @@ namespace tut // Held by a tab on its way here from another window: that tab's. Doc& moving = tab("b", "x\n", ref); r.offerFor(moving, /*held_elsewhere*/ true); - ensure("not offered while another tab holds it", !moving.recoverable && keptFor(was.recoveryKey) == 1); + { + INFO("not offered while another tab holds it"); + REQUIRE_FALSE(moving.recoverable); + CHECK(keptFor(was.recoveryKey) == 1); + } Doc& again = tab("c", "x\n", ref); r.offerFor(again, false); - ensure("offered back", again.recoverable && again.recoverable->text == "x\ntyped"); - ensure("set aside, out of the way", keptFor(again.recoveryKey) == 0); + { + INFO("offered back"); + REQUIRE(again.recoverable); + CHECK(again.recoverable->text == "x\ntyped"); + } + UNSCOPED_INFO("set aside, out of the way"); + CHECK(keptFor(again.recoveryKey) == 0); // Loaded clean, as a tab opened again is: what it keeps of its own // forgets nothing of that. r.keep(again); - ensure("still there to take", std::filesystem::exists(fsyspath(again.recoverable->path))); + UNSCOPED_INFO("still there to take"); + CHECK(std::filesystem::exists(fsyspath(again.recoverable->path))); const Entry offered = *again.recoverable; r.takeUp(again, offered); - ensure_equals("taken up", again.editor->text(), std::string("x\ntyped")); - ensure("its own from here, the set-aside one let go of", keptFor(again.recoveryKey) == 1 && !std::filesystem::exists(fsyspath(offered.path))); + UNSCOPED_INFO("taken up"); + CHECK(again.editor->text() == std::string("x\ntyped")); + { + INFO("its own from here, the set-aside one let go of"); + CHECK(keptFor(again.recoveryKey) == 1); + CHECK_FALSE(std::filesystem::exists(fsyspath(offered.path))); + } } - template<> template<> - void alscriptstudiorecovery_object::test<9>() + TEST_CASE_METHOD(alscriptstudiorecovery_data, "alscriptstudiorecovery: keepAll writes each unsaved tab and reports each failure", "[alscriptstudiorecovery]") { - set_test_name("several tabs kept at once, as the viewer going asks: each unsaved text written, the lot waited on once; a clean tab forgets; one that fails is said"); ALScriptStudioRecovery& r = make(); Doc& a = tab("a", "one\n"); Doc& b = tab("b", "two\n"); Doc& clean = tab("c", "three\n"); type(a, "x"); type(b, "y"); - ensure("kept", r.keepAll({ &a, &b, &clean }, Entry::State::Kept)); - ensure("both written", keptFor(a.recoveryKey) == 1 && keptFor(b.recoveryKey) == 1); - ensure("and nothing of the clean one", keptFor(clean.recoveryKey) == 0); + CHECK(r.keepAll({ &a, &b, &clean }, Entry::State::Kept)); + { + INFO("both written"); + CHECK(keptFor(a.recoveryKey) == 1); + CHECK(keptFor(b.recoveryKey) == 1); + } + UNSCOPED_INFO("and nothing of the clean one"); + CHECK(keptFor(clean.recoveryKey) == 0); const std::vector all = store->list(); - ensure("kept for next time", std::all_of(all.begin(), all.end(), [](const Entry& one) { return one.state == Entry::State::Kept; })); - ensure("nothing said", services().reports.empty()); + UNSCOPED_INFO("kept for next time"); + CHECK(std::all_of(all.begin(), all.end(), [](const Entry& one) { return one.state == Entry::State::Kept; })); + UNSCOPED_INFO("nothing said"); + REQUIRE(services().reports.empty()); const std::string blocked = folder + "/blocked"; { @@ -519,65 +647,89 @@ namespace tut ALRecovery::useStore(&nowhere); type(a, "z"); type(b, "w"); - ensure("not kept", !r.keepAll({ &a, &b })); - ensure("each said once", services().reports.size() == 2 && a.recovery->failed && b.recovery->failed); + CHECK_FALSE(r.keepAll({ &a, &b })); + { + INFO("each said once"); + REQUIRE(services().reports.size() == 2); + CHECK(a.recovery->failed); + CHECK(b.recovery->failed); + } ALRecovery::useStore(store.get()); } - template<> template<> - void alscriptstudiorecovery_object::test<10>() + TEST_CASE_METHOD(alscriptstudiorecovery_data, "alscriptstudiorecovery: keep writes again only when the text or its history has moved", "[alscriptstudiorecovery]") { - set_test_name("nothing is written again where neither the text nor its history has moved; a change, or asking for more, writes it"); ALScriptStudioRecovery& r = make(); Doc& a = tab("a", "one\n"); type(a, "x"); - ensure("kept", r.keep(a) && keptFor(a.recoveryKey) == 1); + CHECK(r.keep(a)); + CHECK(keptFor(a.recoveryKey) == 1); // Taken away behind its back: not written again while nothing moved. for (const Entry& entry : store->list()) { store->remove(entry); } - ensure("kept again, as nothing moved", r.keep(a) && keptFor(a.recoveryKey) == 0); + { + INFO("kept again, as nothing moved"); + CHECK(r.keep(a)); + CHECK(keptFor(a.recoveryKey) == 0); + } r.keepSoon(a); store->flush(); - ensure("nor as typing asks", keptFor(a.recoveryKey) == 0); + UNSCOPED_INFO("nor as typing asks"); + CHECK(keptFor(a.recoveryKey) == 0); a.editor->undoJournal().label("Named"); r.keepSoon(a); store->flush(); - ensure("its history moved: written", keptFor(a.recoveryKey) == 1); + UNSCOPED_INFO("its history moved: written"); + CHECK(keptFor(a.recoveryKey) == 1); for (const Entry& entry : store->list()) { store->remove(entry); } - ensure("kept for next time is more than was asked", r.keep(a, Entry::State::Kept) && keptFor(a.recoveryKey) == 1); + { + INFO("kept for next time is more than was asked"); + CHECK(r.keep(a, Entry::State::Kept)); + CHECK(keptFor(a.recoveryKey) == 1); + } for (const Entry& entry : store->list()) { store->remove(entry); } type(a, "y"); - ensure("typed in: written", r.keep(a) && keptFor(a.recoveryKey) == 1); + { + INFO("typed in: written"); + CHECK(r.keep(a)); + CHECK(keptFor(a.recoveryKey) == 1); + } } - template<> template<> - void alscriptstudiorecovery_object::test<11>() + TEST_CASE_METHOD(alscriptstudiorecovery_data, "alscriptstudiorecovery: at login kept entries are offered only while the studio is closed", "[alscriptstudiorecovery]") { - set_test_name("at login: what sessions left unsaved is offered; what was kept on purpose is offered where the studio is not open to have opened it"); make(); ALRecoveryStore earlier(folder, "earlier"); Entry lost = entry("item:lost", "typed, then a crash"); Entry kept = entry("item:kept", "kept for next time"); kept.state = Entry::State::Kept; - ensure("written", earlier.write(lost) && earlier.write(kept)); + CHECK(earlier.write(lost)); + CHECK(earlier.write(kept)); const ALRecovery::Offers closed = ALRecovery::offersAt(*store, false); - ensure("both offered", closed.unsaved.size() == 1 && closed.kept.size() == 1 && closed.kept.front().key == "item:kept"); + { + INFO("both offered"); + REQUIRE(closed.unsaved.size() == 1); + REQUIRE(closed.kept.size() == 1); + CHECK(closed.kept.front().key == "item:kept"); + } const ALRecovery::Offers open = ALRecovery::offersAt(*store, true); - ensure("the kept not, the studio open having opened it", open.unsaved.size() == 1 && open.kept.empty()); + { + INFO("the kept not, the studio open having opened it"); + REQUIRE(open.unsaved.size() == 1); + REQUIRE(open.kept.empty()); + } } - template<> template<> - void alscriptstudiorecovery_object::test<12>() + TEST_CASE_METHOD(alscriptstudiorecovery_data, "alscriptstudiorecovery: a picked target and experience are kept and picked again on takeUp", "[alscriptstudiorecovery]") { - set_test_name("a compile target and an experience picked for the next save are kept with the text, keep a tab on their own, and are picked again as it is taken up"); ALScriptStudioRecovery& r = make(); Doc& a = tab("a", "one\n"); const LLUUID experience = LLUUID::generateNewID(); @@ -585,32 +737,53 @@ namespace tut a.targetChosen = true; a.experience = experience; a.experienceChosen = true; - ensure("a pick alone is unsaved, and kept", r.keep(a) && keptFor(a.recoveryKey) == 1); + { + INFO("a pick alone is unsaved, and kept"); + CHECK(r.keep(a)); + CHECK(keptFor(a.recoveryKey) == 1); + } type(a, "x"); const Entry written = ALScriptStudioRecovery::entryOf(a); - ensure("the picks with the entry", written.pickedTarget == std::optional("lsl2") && written.pickedExperience == std::optional(experience)); - ensure("kept", r.keep(a)); + { + INFO("the picks with the entry"); + CHECK(written.pickedTarget == std::optional("lsl2")); + CHECK(written.pickedExperience == std::optional(experience)); + } + CHECK(r.keep(a)); std::optional back = store->leftFor(a.recoveryKey); - ensure("not another session's", !back); + UNSCOPED_INFO("not another session's"); + REQUIRE_FALSE(back); const std::vector listed = store->list(); Entry read = listed.front(); - ensure("read whole, the picks with it", store->load(read) && read.pickedTarget == written.pickedTarget && read.pickedExperience == written.pickedExperience); + { + INFO("read whole, the picks with it"); + CHECK(store->load(read)); + CHECK(read.pickedTarget == written.pickedTarget); + CHECK(read.pickedExperience == written.pickedExperience); + } Doc& loaded = tab("b", "one\n", a.ref); r.takeUp(loaded, read); - ensure("picked again over a tab that has loaded", loaded.targetChosen && loaded.language.compileTarget == "lsl2" && loaded.experienceChosen && - loaded.experience == experience); + { + INFO("picked again over a tab that has loaded"); + CHECK(loaded.targetChosen); + CHECK(loaded.language.compileTarget == "lsl2"); + CHECK(loaded.experienceChosen); + CHECK(loaded.experience == experience); + } Doc& loading = tab("c", "one\n"); loading.loaded = false; r.takeUp(loading, read); - ensure("carried to be picked as a tab loading loads", loading.carriedTarget == std::optional("lsl2") && - loading.carriedExperience == std::optional(experience) && !loading.targetChosen); + { + INFO("carried to be picked as a tab loading loads"); + CHECK(loading.carriedTarget == std::optional("lsl2")); + CHECK(loading.carriedExperience == std::optional(experience)); + CHECK_FALSE(loading.targetChosen); + } } - template<> template<> - void alscriptstudiorecovery_object::test<13>() + TEST_CASE_METHOD(alscriptstudiorecovery_data, "alscriptstudiorecovery: a notecard window's entry goes back to it unless it cannot take it", "[alscriptstudiorecovery]") { - set_test_name("what the notecard window kept goes back to it, under a key of its own; where it cannot have the notecard, the studio takes it up"); ALScriptStudioRecovery& r = make(); Entry card; card.name = "card"; @@ -619,8 +792,12 @@ namespace tut card.notecard = true; card.text = "kept\n"; card.key = ALRecoveryStore::windowKeyOf(card.object, card.item); - ensure("a key of its own", ALRecoveryStore::isWindowKey(card.key) && card.key != ALRecoveryStore::keyOf(card.object, card.item, std::string()) && - !ALRecoveryStore::isWindowKey(ALRecoveryStore::keyOf(card.object, card.item, std::string()))); + { + INFO("a key of its own"); + CHECK(ALRecoveryStore::isWindowKey(card.key)); + CHECK(card.key != ALRecoveryStore::keyOf(card.object, card.item, std::string())); + CHECK_FALSE(ALRecoveryStore::isWindowKey(ALRecoveryStore::keyOf(card.object, card.item, std::string()))); + } std::vector taken; bool has = true; @@ -630,39 +807,57 @@ namespace tut }); studio().inHand = false; r.recover(card); - ensure("taken up in the window", taken.size() == 1 && studio().orphans.empty() && services().opened.empty()); + { + INFO("taken up in the window"); + REQUIRE(taken.size() == 1); + REQUIRE(studio().orphans.empty()); + REQUIRE(services().opened.empty()); + } // A studio tab's own entry for the same notecard is the studio's. Entry tab = card; tab.key = ALRecoveryStore::keyOf(card.object, card.item, std::string()); r.recover(tab); - ensure("a tab's entry is not the window's", taken.size() == 1 && studio().orphans.size() == 1); + { + INFO("a tab's entry is not the window's"); + REQUIRE(taken.size() == 1); + REQUIRE(studio().orphans.size() == 1); + } has = false; r.recover(card); - ensure("the window cannot have it: the studio keeps it", taken.size() == 2 && studio().orphans.size() == 2); + { + INFO("the window cannot have it: the studio keeps it"); + REQUIRE(taken.size() == 2); + REQUIRE(studio().orphans.size() == 2); + } ALRecovery::takeWindowsTo(nullptr); } - template<> template<> - void alscriptstudiorecovery_object::test<14>() + TEST_CASE_METHOD(alscriptstudiorecovery_data, "alscriptstudiorecovery: rekey lets go of the old key's entry and writes under the new one", "[alscriptstudiorecovery]") { - set_test_name("a tab kept under another key from here on: what the old one kept let go of, and written afresh under the new"); ALScriptStudioRecovery& r = make(); Doc& a = tab("a", "one\n"); type(a, "x"); - ensure("kept", r.keep(a) && keptFor(a.recoveryKey) == 1); + CHECK(r.keep(a)); + CHECK(keptFor(a.recoveryKey) == 1); const std::string was = a.recoveryKey; ALScriptStudioRecovery::rekey(a, ALRecoveryStore::keyOf(LLUUID::null, LLUUID::null, "/somewhere/a.lsl")); store->flush(); - ensure("the old let go of", a.recoveryKey != was && keptFor(was) == 0); - ensure("written afresh, though nothing moved", r.keep(a) && keptFor(a.recoveryKey) == 1); + { + INFO("the old let go of"); + CHECK(a.recoveryKey != was); + CHECK(keptFor(was) == 0); + } + { + INFO("written afresh, though nothing moved"); + CHECK(r.keep(a)); + CHECK(keptFor(a.recoveryKey) == 1); + } } - template<> template<> - void alscriptstudiorecovery_object::test<15>() + TEST_CASE_METHOD(alscriptstudiorecovery_data, "alscriptstudiorecovery: keepSaved records on the writer's thread and moves saves to a new item", "[alscriptstudiorecovery]") { - set_test_name("a save kept in the history on the writer's thread; one given a new item takes the old one's saves with it"); const std::string folder = fsyspath(std::filesystem::temp_directory_path() / fsyspath("alsavehistory_" + LLUUID::generateNewID().asString())).string(); auto history = std::make_shared(folder); @@ -701,10 +896,12 @@ namespace tut ALRecovery::keepSaved(save("task:p:old", "a", 20.0)); ALRecovery::keepSaved(save("task:p:new", "b", 10.0), "task:p:old"); written(); - ensure_equals("kept", history->list("item:x").size(), 1U); - ensure("the old item's moved", history->list("task:p:old").empty()); + REQUIRE(history->list("item:x").size() == 1U); + UNSCOPED_INFO("the old item's moved"); + REQUIRE(history->list("task:p:old").empty()); const std::vector moved = history->list("task:p:new"); - ensure_equals("behind the new one's", moved.size(), 2U); + UNSCOPED_INFO("behind the new one's"); + REQUIRE(moved.size() == 2U); ALRecovery::keepSaved(save("", "nobody's", 5.0)); written(); } diff --git a/indra/newview/tests/alscriptstudiosaving_test.cpp b/indra/newview/tests/alscriptstudiosaving_test.cpp index f5948e6d7c..c41f2ac5fa 100644 --- a/indra/newview/tests/alscriptstudiosaving_test.cpp +++ b/indra/newview/tests/alscriptstudiosaving_test.cpp @@ -33,7 +33,7 @@ #include "../alscriptstudiofileio.h" #include "alscriptstudio_fixture.h" -#include "../test/lltut.h" +#include #include @@ -176,7 +176,7 @@ namespace } } -namespace tut +namespace { struct alscriptstudiosaving_data { @@ -197,7 +197,7 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } studio.services = &services; studio.options.program = "Alchemy Test 1.2.3"; @@ -261,55 +261,75 @@ namespace tut std::string lastStatus() const { return services.statuses.empty() ? std::string() : nameOf(services.statuses.back()); } }; - typedef test_group alscriptstudiosaving_group; - typedef alscriptstudiosaving_group::object alscriptstudiosaving_object; - alscriptstudiosaving_group alscriptstudiosaving_instance("alscriptstudiosaving"); - - template<> template<> - void alscriptstudiosaving_object::test<1>() + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: a save writes a file or sends a notecard and holds one away or detached", "[alscriptstudiosaving]") { - set_test_name("a save goes as its tab is: a file written, a notecard sent as it stands, one out of reach said, one detached loaded first"); ALScriptStudioSaving& saving = make(); Doc& file = tab("file", "x = 1"); file.file = files->in("x.luau"); saving.save(file); std::string written; - ensure("a file written, nothing sent", files->settled == Names{ "file" } && ALScriptFileIO::readWholeFile(file.file, written) && written == "x = 1" && - studio.sent.empty()); + { + INFO("a file written, nothing sent"); + CHECK(files->settled == Names{ "file" }); + CHECK(ALScriptFileIO::readWholeFile(file.file, written)); + CHECK(written == "x = 1"); + CHECK(studio.sent.empty()); + } Doc& card = tab("card", "some words"); card.notecard = true; // Kept against a crash before, which a save lets go of. recovery->store->write(ALScriptStudioRecovery::entryOf(card)); - ensure("kept before", recovery->keptFor(card.recoveryKey) == 1); + UNSCOPED_INFO("kept before"); + CHECK(recovery->keptFor(card.recoveryKey) == 1); saving.save(card); - ensure("a notecard sent as it stands", studio.notecards == Names{ "some words" }); - ensure("and not tidied", studio.tidied.size() == 1); + UNSCOPED_INFO("a notecard sent as it stands"); + CHECK(studio.notecards == Names{ "some words" }); + UNSCOPED_INFO("the notecard not tidied: the one tidy the file's"); + CHECK(studio.tidied.size() == 1); CompileResult saved = answer(card); saved.kind = ALScriptKind::Notecard; saving.compiled(saved); - ensure_equals("its answer said", lastSaid(), std::string("SavedNotecard")); - ensure("and kept as saved: what was kept let go of", recovery->keptFor(card.recoveryKey) == 0 && !card.save.underway()); + UNSCOPED_INFO("its answer said"); + CHECK(lastSaid() == std::string("SavedNotecard")); + { + INFO("and kept as saved: what was kept let go of"); + CHECK(recovery->keptFor(card.recoveryKey) == 0); + CHECK_FALSE(card.save.underway()); + } Doc& away = tab("away", "default {}"); away.orphan->kind = Doc::Orphan::Away; services.front = 2; saving.save(away); - ensure_equals("out of reach, said where", lastStatus(), std::string("SaveBlockedAway")); - ensure("stopped, and the notice back", studio.stops == 1 && studio.notices == 1 && !away.save.underway() && studio.sent.empty()); + UNSCOPED_INFO("out of reach, said where"); + CHECK(lastStatus() == std::string("SaveBlockedAway")); + { + INFO("stopped, and the notice back"); + CHECK(studio.stops == 1); + CHECK(studio.notices == 1); + CHECK_FALSE(away.save.underway()); + CHECK(studio.sent.empty()); + } Doc& detached = tab("detached", "default {}"); detached.orphan->detached = true; saving.save(detached); - ensure("loaded under its item first", orphans->loads.size() == 1 && orphans->loads[0] == detached.ref && studio.sent.empty()); - ensure_equals("and said to wait", lastStatus(), std::string("SaveWaitsForLoad")); + { + INFO("loaded under its item first"); + REQUIRE(orphans->loads.size() == 1); + CHECK(orphans->loads[0] == detached.ref); + CHECK(studio.sent.empty()); + } + UNSCOPED_INFO("and said to wait"); + CHECK(lastStatus() == std::string("SaveWaitsForLoad")); } - template<> template<> - void alscriptstudiosaving_object::test<2>() + // Tidied as the settings say, and fixed once a save however many checks + // it waits on. + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: a save is tidied first with fixes only over the text last checked", "[alscriptstudiosaving]") { - set_test_name("tidied first as the settings say: the safe fixes only over the text last checked, and once a save however many checks it waits on"); ALScriptStudioSaving& saving = make(); studio.options.fix = true; studio.options.format = true; @@ -318,63 +338,100 @@ namespace tut Doc& unchecked = tab("unchecked", "default {}"); saving.save(unchecked); - ensure_equals("no fixes over a text not checked", studio.tidied.back(), std::string("mt")); + UNSCOPED_INFO("no fixes over a text not checked"); + CHECK(studio.tidied.back() == std::string("mt")); Doc& doc = tab("a", "default {}"); checked(doc); saving.save(doc); - ensure_equals("the fixes, over the text checked", studio.tidied.back(), std::string("fmt")); + UNSCOPED_INFO("the fixes, over the text checked"); + CHECK(studio.tidied.back() == std::string("fmt")); // They changed the text: checked again before it goes. - ensure("checked again", studio.analysed.back() == "a" && studio.sent.empty()); + { + INFO("checked again"); + CHECK(studio.analysed.back() == "a"); + CHECK(studio.sent.empty()); + } checked(doc); - ensure("the check waited on", doc.save.checked()); + UNSCOPED_INFO("the check waited on"); + CHECK(doc.save.checked()); saving.save(doc); - ensure_equals("not fixed twice", studio.tidied.back(), std::string("mt")); - ensure("sent", studio.sent.size() == 1 && studio.sent.back().doc == "a"); + UNSCOPED_INFO("not fixed twice"); + CHECK(studio.tidied.back() == std::string("mt")); + { + INFO("sent"); + REQUIRE(studio.sent.size() == 1); + CHECK(studio.sent.back().doc == "a"); + } } - template<> template<> - void alscriptstudiosaving_object::test<3>() + // Checked first, their errors stop it with Save Anyway and the first in + // sight; asked again, it goes past them and the offer is answered. + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: a save held on the analyzers stops at their errors until asked again", "[alscriptstudiosaving]") { - set_test_name("held on the analyzers: checked first, their errors stopping it with Save Anyway and the first in sight; asked again, past them, the offer answered"); ALScriptStudioSaving& saving = make(); studio.options.holdOnErrors = true; Doc& doc = tab("a", "default {}"); saving.save(doc); - ensure("checked first", studio.analysed == Names{ "a" } && studio.sent.empty()); - ensure_equals("and said so", lastStatus(), std::string("Preflight")); + { + INFO("checked first"); + CHECK(studio.analysed == Names{ "a" }); + CHECK(studio.sent.empty()); + } + UNSCOPED_INFO("and said so"); + CHECK(lastStatus() == std::string("Preflight")); checked(doc, { anError() }); - ensure("the check waited on", doc.save.checked()); + UNSCOPED_INFO("the check waited on"); + CHECK(doc.save.checked()); saving.save(doc); - ensure_equals("its errors stop it", lastSaid(), std::string("PreflightErrors")); + UNSCOPED_INFO("its errors stop it"); + CHECK(lastSaid() == std::string("PreflightErrors")); const al_studio_test::FakeServices::Said& said = services.reports.back(); - ensure("a failure, of the tab, with Save Anyway", said.failure && said.doc == "a" && said.actions == Names{ "save_anyway" }); - ensure("the first of the checkers' errors in sight", studio.problemsShown == 1 && studio.firstErrors == std::vector{ true }); - ensure("stopped", studio.stops == 1 && studio.sent.empty() && !doc.save.underway()); + { + INFO("a failure, of the tab, with Save Anyway"); + CHECK(said.failure); + CHECK(said.doc == "a"); + CHECK(said.actions == Names{ "save_anyway" }); + } + { + INFO("the first of the checkers' errors in sight"); + CHECK(studio.problemsShown == 1); + CHECK(studio.firstErrors == std::vector{ true }); + } + { + INFO("stopped"); + CHECK(studio.stops == 1); + CHECK(studio.sent.empty()); + CHECK_FALSE(doc.save.underway()); + } doc.offer = Doc::Offer{ "held", { "save_anyway" } }; saving.saveAsked(doc); - ensure("asked again over the same text: past them", studio.sent.size() == 1); - ensure("what was offered answered", !doc.offer); + UNSCOPED_INFO("asked again over the same text: past them"); + CHECK(studio.sent.size() == 1); + UNSCOPED_INFO("what was offered answered"); + CHECK_FALSE(doc.offer); doc.offer = Doc::Offer{ "saved elsewhere", { "take_saved", "keep_saved", "compare_saved" } }; saving.save(doc); - ensure("a conflict's offer stays", doc.offer.has_value()); + UNSCOPED_INFO("a conflict's offer stays"); + CHECK(doc.offer.has_value()); // Not held, the errors go with the text, said. studio.options.holdOnErrors = false; Doc& free = tab("free", "default {}"); checked(free, { anError() }); saving.save(free); - ensure("sent", studio.sent.size() == 2); - ensure_equals("with its errors said", lastSaid(), std::string("SentWithErrors")); + CHECK(studio.sent.size() == 2); + UNSCOPED_INFO("with its errors said"); + CHECK(lastSaid() == std::string("SentWithErrors")); } - template<> template<> - void alscriptstudiosaving_object::test<4>() + // Its target, whether it runs and its experience; marked saved as it + // went. + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: a save sends the tab's own options and stops when too large or refused", "[alscriptstudiosaving]") { - set_test_name("sent as the tab's own -- its target, whether it runs, its experience -- marked saved as it went; too large or refused, said and stopped"); ALScriptStudioSaving& saving = make(); Doc& doc = tab("a", "default {}"); @@ -386,28 +443,42 @@ namespace tut doc.experienceChosen = true; type(doc, "\n// more"); saving.save(doc); - ensure_equals("one sent", studio.sent.size(), size_t(1)); + REQUIRE(studio.sent.size() == size_t(1)); const FakeSavingWindow::Sent& sent = studio.sent.back(); - ensure_equals("the text as it stands", sent.text, std::string("default {}\n// more")); - ensure("its target, not running, its experience", - sent.options.compileTarget == "mono" && sent.options.running == false && sent.options.experience == experience); - ensure("on its way", doc.save.sending()); - ensure_equals("said", lastStatus(), std::string("Saving")); + UNSCOPED_INFO("the text as it stands"); + CHECK(sent.text == std::string("default {}\n// more")); + { + INFO("its target, not running, its experience"); + CHECK(sent.options.compileTarget == "mono"); + CHECK(sent.options.running == false); + CHECK(sent.options.experience == experience); + } + UNSCOPED_INFO("on its way"); + CHECK(doc.save.sending()); + CHECK(lastStatus() == std::string("Saving")); // Typed while it was on its way: that is still to be saved. type(doc, "\n// later"); saving.compiled(answer(doc)); - ensure_equals("compiled", lastSaid(), std::string("Compiled")); - ensure("what was typed since still unsaved", doc.editor->isDirty()); - ensure("the experience it went with is the one it runs under", !doc.experienceChosen && doc.experienceKnown); - ensure("kept for recovery as it is now", recovery->keptFor(doc.recoveryKey) == 1); + CHECK(lastSaid() == std::string("Compiled")); + UNSCOPED_INFO("what was typed since still unsaved"); + CHECK(doc.editor->isDirty()); + { + INFO("the experience it went with is the one it runs under"); + CHECK_FALSE(doc.experienceChosen); + CHECK(doc.experienceKnown); + } + UNSCOPED_INFO("kept for recovery as it is now"); + CHECK(recovery->keptFor(doc.recoveryKey) == 1); Doc& clean = tab("clean", "default {}"); type(clean, " "); saving.save(clean); - ensure("whether it runs not heard yet: not said, for the save to ask", !studio.sent.back().options.running.has_value()); + UNSCOPED_INFO("whether it runs not heard yet: not said, for the save to ask"); + CHECK_FALSE(studio.sent.back().options.running.has_value()); saving.compiled(answer(clean)); - ensure("nothing typed since: saved", !clean.unsaved()); + UNSCOPED_INFO("nothing typed since: saved"); + CHECK_FALSE(clean.unsaved()); std::string too_much; while (too_much.size() <= ALScriptEnvelope::MAX_ASSET_BYTES) @@ -416,71 +487,111 @@ namespace tut } Doc& big = tab("big", too_much); saving.save(big); - ensure_equals("too large: said", lastSaid(), std::string("SaveTooLarge")); - ensure("with what would shrink it", services.reports.back().text.find("SaveTooLargePlain") != std::string::npos); - ensure("its size kept, nothing sent, stopped", - big.weighing->assetBytes == too_much.size() && studio.sent.size() == 2 && !big.save.underway() && studio.stops == 1); + UNSCOPED_INFO("too large: said"); + CHECK(lastSaid() == std::string("SaveTooLarge")); + UNSCOPED_INFO("with what would shrink it"); + CHECK(services.reports.back().text.find("SaveTooLargePlain") != std::string::npos); + { + INFO("its size kept, nothing sent, stopped"); + CHECK(big.weighing->assetBytes == too_much.size()); + CHECK(studio.sent.size() == 2); + CHECK_FALSE(big.save.underway()); + CHECK(studio.stops == 1); + } studio.refuse = "no region"; Doc& refused = tab("refused", "default {}"); saving.save(refused); - ensure_equals("refused: said", lastSaid(), std::string("SaveFailed")); - ensure("with what may be done", services.reports.back().actions == Names{ "retry", "copy", "export" }); - ensure("stopped", !refused.save.underway() && studio.stops == 2); + UNSCOPED_INFO("refused: said"); + CHECK(lastSaid() == std::string("SaveFailed")); + UNSCOPED_INFO("with what may be done"); + CHECK(services.reports.back().actions == Names{ "retry", "copy", "export" }); + { + INFO("stopped"); + CHECK_FALSE(refused.save.underway()); + CHECK(studio.stops == 2); + } } - template<> template<> - void alscriptstudiosaving_object::test<5>() + // Preprocessed first, its errors said, what it made in the envelope with + // its map; a text moved on meanwhile is run again. + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: a preprocessed save goes with its errors and what it made enveloped", "[alscriptstudiosaving]") { - set_test_name("preprocessed first: sent with its errors said, what it made in the envelope with its map, and a text moved on is run again"); ALScriptStudioSaving& saving = make(); studio.preprocessor = true; Doc& doc = tab("a", "#include \"x.lsl\"\ndefault {}"); saving.save(doc); - ensure("run first", studio.runs == Names{ "a" } && studio.sent.empty() && doc.preprocessing); + { + INFO("run first"); + CHECK(studio.runs == Names{ "a" }); + CHECK(studio.sent.empty()); + CHECK(doc.preprocessing); + } // Its errors do not stop it: the save is what keeps the work. ALPreprocessor::Result wrong; wrong.text = "default {"; wrong.problems.push_back(anError()); studio.answerRun(wrong); - ensure("what waited on the map it made told", studio.known == Names{ "a" }); - ensure_equals("sent all the same", studio.sent.size(), size_t(1)); - ensure_equals("said as it went", lastSaid(), std::string("SavingWithErrors")); - ensure("a failure of the tab, nothing to press", services.reports.back().failure && services.reports.back().actions.empty()); - ensure("not stopped, the problems in sight", doc.save.underway() && studio.stops == 0 && studio.problemsShown == 1); + UNSCOPED_INFO("what waited on the map it made told"); + CHECK(studio.known == Names{ "a" }); + UNSCOPED_INFO("sent all the same"); + CHECK(studio.sent.size() == size_t(1)); + UNSCOPED_INFO("said as it went"); + CHECK(lastSaid() == std::string("SavingWithErrors")); + { + INFO("a failure of the tab, nothing to press"); + CHECK(services.reports.back().failure); + CHECK(services.reports.back().actions.empty()); + } + { + INFO("not stopped, the problems in sight"); + CHECK(doc.save.underway()); + CHECK(studio.stops == 0); + CHECK(studio.problemsShown == 1); + } saving.compiled(answer(doc)); type(doc, " "); saving.save(doc); - ensure("run again", studio.runs.size() == 2); + UNSCOPED_INFO("run again"); + REQUIRE(studio.runs.size() == 2); ALPreprocessor::Result made; made.text = "default { state_entry() {} }"; studio.answerRun(made); - ensure_equals("sent", studio.sent.size(), size_t(2)); + REQUIRE(studio.sent.size() == size_t(2)); const std::optional envelope = ALScriptEnvelope::parse(studio.sent.back().text); - ensure("in the envelope", envelope.has_value()); - ensure_equals("what it made", envelope->expanded, made.text); - ensure_equals("by this viewer", envelope->programVersion, studio.options.program); - ensure("with its map, for the compiler's lines", doc.save.sentMap().has_value()); - ensure("weighed as each went", std::count(weighing->weighs.begin(), weighing->weighs.end(), "sent") == 2); + UNSCOPED_INFO("in the envelope"); + REQUIRE(envelope.has_value()); + UNSCOPED_INFO("what it made"); + CHECK(envelope->expanded == made.text); + UNSCOPED_INFO("by this viewer"); + CHECK(envelope->programVersion == studio.options.program); + UNSCOPED_INFO("with its map, for the compiler's lines"); + CHECK(doc.save.sentMap().has_value()); + UNSCOPED_INFO("weighed as each went"); + CHECK(std::count(weighing->weighs.begin(), weighing->weighs.end(), "sent") == 2); saving.compiled(answer(doc)); // The text moved on while the includes came: checked, and run again. type(doc, "\n// a"); saving.save(doc); - ensure("run", studio.runs.size() == 3); + REQUIRE(studio.runs.size() == 3); type(doc, "\n// b"); studio.answerRun(made); - ensure("checked again", studio.analysed.back() == "a"); - ensure("and run again, nothing sent", studio.runs.size() == 4 && studio.sent.size() == 2); + UNSCOPED_INFO("checked again"); + CHECK(studio.analysed.back() == "a"); + { + INFO("and run again, nothing sent"); + CHECK(studio.runs.size() == 4); + CHECK(studio.sent.size() == 2); + } } - template<> template<> - void alscriptstudiosaving_object::test<6>() + // And a tab saved to close is let go of. + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: the compiler's answer keeps its errors and sends a save asked meanwhile", "[alscriptstudiosaving]") { - set_test_name("the compiler's answer: its errors kept and in sight, a save asked meanwhile sent after it, and a tab saved to close let go of"); ALScriptStudioSaving& saving = make(); Doc& doc = tab("a", "default {}"); @@ -494,27 +605,52 @@ namespace tut diagnostic.message = "syntax error"; failed.diagnostics.push_back(diagnostic); saving.compiled(failed); - ensure_equals("said", lastSaid(), std::string("CompileFailed")); - ensure("kept", doc.problems.size() == 1 && doc.problems[0].line == 3 && doc.problems[0].message == "syntax error"); - ensure("in sight, its first error chosen", studio.problemsShown == 1 && studio.firstErrors == std::vector{ false }); - ensure("a close waiting on it stopped", studio.stops == 1); + CHECK(lastSaid() == std::string("CompileFailed")); + { + INFO("its errors kept"); + REQUIRE(doc.problems.size() == 1); + CHECK(doc.problems[0].line == 3); + CHECK(doc.problems[0].message == "syntax error"); + } + { + INFO("in sight, its first error chosen"); + CHECK(studio.problemsShown == 1); + CHECK(studio.firstErrors == std::vector{ false }); + } + UNSCOPED_INFO("a close waiting on it stopped"); + CHECK(studio.stops == 1); type(doc, " "); saving.save(doc); type(doc, " "); saving.save(doc); - ensure_equals("one on its way at a time", studio.sent.size(), size_t(2)); + UNSCOPED_INFO("one on its way at a time"); + CHECK(studio.sent.size() == size_t(2)); saving.compiled(answer(doc)); - ensure("the one asked meanwhile sent after it", studio.sent.size() == 3 && doc.save.sending()); - ensure("what the last said gone with it", doc.problems.empty()); + { + INFO("the one asked meanwhile sent after it"); + CHECK(studio.sent.size() == 3); + CHECK(doc.save.sending()); + } + UNSCOPED_INFO("what the last said gone with it"); + CHECK(doc.problems.empty()); saving.compiled(answer(doc)); type(doc, " "); saving.saveToClose("a"); - ensure("sent to close", studio.sent.size() == 4 && doc.save.closeAfter()); + { + INFO("sent to close"); + CHECK(studio.sent.size() == 4); + CHECK(doc.save.closeAfter()); + } saving.compiled(answer(doc)); - ensure("let go of", studio.closed == Names{ "a" } && !services.findDoc(std::string_view("a"))); - ensure("and a window closing gone on with", studio.continues == 1); + { + INFO("let go of"); + CHECK(studio.closed == Names{ "a" }); + CHECK_FALSE(services.findDoc(std::string_view("a"))); + } + UNSCOPED_INFO("and a window closing gone on with"); + CHECK(studio.continues == 1); Doc& card = tab("card", "some words"); card.notecard = true; @@ -523,19 +659,26 @@ namespace tut CompileResult saved = answer(card); saved.kind = ALScriptKind::Notecard; saving.compiled(saved); - ensure("a notecard the same", studio.closed == Names{ "a", "card" } && studio.continues == 2); + { + INFO("a notecard the same"); + CHECK(studio.closed == Names{ "a", "card" }); + CHECK(studio.continues == 2); + } Doc& loading = tab("loading", ""); loading.loaded = false; saving.saveToClose("loading"); - ensure("one that cannot begin is left as it was", services.findDoc(std::string_view("loading")) && !loading.save.closeAfter()); - ensure("and a close waiting on it stops", studio.stops == 2); + { + INFO("one that cannot begin is left as it was"); + REQUIRE(services.findDoc(std::string_view("loading"))); + CHECK_FALSE(loading.save.closeAfter()); + } + UNSCOPED_INFO("and a close waiting on it stops"); + CHECK(studio.stops == 2); } - template<> template<> - void alscriptstudiosaving_object::test<7>() + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: a copy saved closes its original unless that was typed in since", "[alscriptstudiosaving]") { - set_test_name("a copy saved closes the tab it was made of, unless that was typed in since"); ALScriptStudioSaving& saving = make(); Doc& original = tab("original", "default {}"); @@ -545,9 +688,11 @@ namespace tut type(copy, " "); saving.save(copy); saving.compiled(answer(copy)); - ensure("the original let go of", studio.closed == Names{ "original" }); - ensure_equals("said", lastSaid(), std::string("CopiedTo")); - ensure("the copy is no copy now", copy.copyOf.empty()); + UNSCOPED_INFO("the original let go of"); + CHECK(studio.closed == Names{ "original" }); + CHECK(lastSaid() == std::string("CopiedTo")); + UNSCOPED_INFO("the copy is no copy now"); + CHECK(copy.copyOf.empty()); Doc& typed = tab("typed", "default {}"); Doc& other = tab("other", "default {}"); @@ -557,21 +702,27 @@ namespace tut type(other, " "); saving.save(other); saving.compiled(answer(other)); - ensure("typed in since: kept open", studio.closed.size() == 1 && services.findDoc(std::string_view("typed"))); - ensure_equals("and said so", lastSaid(), std::string("CopiedToKeptOpen")); - ensure_equals("of it", services.reports.back().doc, std::string("typed")); + { + INFO("typed in since: kept open"); + CHECK(studio.closed.size() == 1); + CHECK(services.findDoc(std::string_view("typed"))); + } + UNSCOPED_INFO("and said so"); + CHECK(lastSaid() == std::string("CopiedToKeptOpen")); + UNSCOPED_INFO("said of the tab kept open"); + CHECK(services.reports.back().doc == std::string("typed")); } - template<> template<> - void alscriptstudiosaving_object::test<8>() + // What a save sends is weighed as it goes, and its weight over the limit + // said where it is exact. + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: a save over its target's limit is said once and an estimate as one", "[alscriptstudiosaving]") { - set_test_name("what a save sends weighed as it goes, and over its target's limit said once, where the weight is exact; an estimate's said as one"); ALScriptStudioSaving& saving = make(); Doc& doc = tab("a", "default {}"); type(doc, " "); saving.save(doc); - ensure("weighed", weighing->weighs == Names{ "text" }); + CHECK(weighing->weighs == Names{ "text" }); ALScriptWeight weight; weight.target = ALScriptWeight::Target::Mono; weight.total = 70000; @@ -580,11 +731,13 @@ namespace tut doc.weighing->version = doc.editor->document().version(); doc.weighing->exact = true; saving.warnOverWeight(doc); - ensure_equals("over: said", lastSaid(), std::string("SaveOverWeight")); - ensure("as a failure", services.reports.back().failure); + UNSCOPED_INFO("over: said"); + CHECK(lastSaid() == std::string("SaveOverWeight")); + CHECK(services.reports.back().failure); const size_t said = services.reports.size(); saving.warnOverWeight(doc); - ensure("once", services.reports.size() == said); + UNSCOPED_INFO("not said again"); + CHECK(services.reports.size() == said); Doc& guessed = tab("guessed", "default {}"); type(guessed, " "); @@ -594,37 +747,39 @@ namespace tut guessed.weighing->version = guessed.editor->document().version(); guessed.weighing->exact = true; saving.warnOverWeight(guessed); - ensure_equals("an estimate said as one", lastSaid(), std::string("SaveOverWeightEstimate")); - ensure("once too", services.reports.size() == said + 1); + UNSCOPED_INFO("an estimate said as one"); + CHECK(lastSaid() == std::string("SaveOverWeightEstimate")); + UNSCOPED_INFO("once too"); + CHECK(services.reports.size() == said + 1); saving.warnOverWeight(guessed); - ensure("and only once", services.reports.size() == said + 1); + UNSCOPED_INFO("and only once"); + CHECK(services.reports.size() == said + 1); } - template<> template<> - void alscriptstudiosaving_object::test<9>() + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: a save the external editor made is not tidied but one made here after is", "[alscriptstudiosaving]") { - set_test_name("a save the external editor made is not tidied, and one made here after it is"); ALScriptStudioSaving& saving = make(); studio.options.format = true; studio.options.trim = true; Doc& doc = tab("a", "default {}"); doc.save.fromExternal(doc.editor->document().version()); saving.save(doc); - ensure("not tidied", studio.tidied.empty()); - ensure("sent as it came", studio.sent.size() == 1); + CHECK(studio.tidied.empty()); + UNSCOPED_INFO("sent as it came"); + CHECK(studio.sent.size() == 1); saving.compiled(answer(doc)); // Ended as the answer ends it where the editor is watched. doc.save.endExternal(); doc.editor->setCaret(doc.editor->document().end()); doc.editor->insertText(" "); saving.save(doc); - ensure_equals("tidied, made here", studio.tidied.size(), size_t(1)); + UNSCOPED_INFO("tidied, made here"); + CHECK(studio.tidied.size() == size_t(1)); } - template<> template<> - void alscriptstudiosaving_object::test<10>() + // Its first run as it loaded is held up to its compiled half. + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: a wrapped script offers a compiled half its source cannot have made", "[alscriptstudiosaving]") { - set_test_name("a wrapped script's first run as it loaded held up to its compiled half: offered where the source could not have made it"); ALScriptStudioSaving& saving = make(); studio.preprocessor = true; const std::string source = "default { state_entry() { llSay(0, \"hi\"); } }"; @@ -649,40 +804,53 @@ namespace tut Doc& here = loaded("here", edited, "Alchemy Release 7.2.0"); ran(here); - ensure("ours, edited outside: offered", here.compiledDiffers && *here.compiledDiffers == edited && !here.compareCompiledAt); + { + INFO("ours, edited outside: offered"); + REQUIRE(here.compiledDiffers); + CHECK(*here.compiledDiffers == edited); + CHECK_FALSE(here.compareCompiledAt); + } Doc& same = loaded("same", "default{state_entry(){llSay(0,\"hi\");}}", "Alchemy Release 7.2.0"); ran(same); - ensure("what the source makes, spaced otherwise: nothing", !same.compiledDiffers); + UNSCOPED_INFO("what the source makes, spaced otherwise: nothing"); + CHECK_FALSE(same.compiledDiffers); Doc& firestorm = loaded("fs", edited, "Firestorm-Releasex64 7.1.11.76496"); ran(firestorm); - ensure("Firestorm's, untransformed: told", firestorm.compiledDiffers.has_value()); + UNSCOPED_INFO("Firestorm's, untransformed: told"); + CHECK(firestorm.compiledDiffers.has_value()); Doc& switched = loaded("sw", edited, "Firestorm-Releasex64 7.1.11.76496"); ran(switched, true); - ensure("Firestorm's, with a transform whose names ours need not share: not told", !switched.compiledDiffers); + UNSCOPED_INFO("Firestorm's, with a transform whose names ours need not share: not told"); + CHECK_FALSE(switched.compiledDiffers); Doc& typed = loaded("typed", edited, "Alchemy Release 7.2.0"); type(typed, " "); ran(typed); - ensure("typed in before the run: not the text as it came", !typed.compiledDiffers && !typed.compareCompiledAt); + { + INFO("typed in before the run: not the text as it came"); + CHECK_FALSE(typed.compiledDiffers); + CHECK_FALSE(typed.compareCompiledAt); + } } - template<> template<> - void alscriptstudiosaving_object::test<11>() + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: a tidy is one step to undo however many of its parts changed the text", "[alscriptstudiosaving]") { - set_test_name("tidied as one step to undo, however many of its parts changed the text"); ALScriptStudioSaving& saving = make(); studio.options.format = true; studio.options.trim = true; studio.changes = true; Doc& doc = tab("a", "default {}"); saving.save(doc); - ensure_equals("each part changed it", doc.editor->text(), std::string("mdefault {}t")); + UNSCOPED_INFO("each part changed it"); + CHECK(doc.editor->text() == std::string("mdefault {}t")); doc.editor->undo(); - ensure_equals("one Undo takes back all of it", doc.editor->text(), std::string("default {}")); + UNSCOPED_INFO("one Undo takes back all of it"); + CHECK(doc.editor->text() == std::string("default {}")); } - template<> template<> - void alscriptstudiosaving_object::test<12>() + + // An include not found, or one that never came, said by name with the + // disk's route, and the other errors said besides. + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: a save goes up without includes missing or pending and names them", "[alscriptstudiosaving]") { - set_test_name("a save goes up without an include not found, or one that never came, saying so by name with the disk's route, and with the other errors said"); ALScriptStudioSaving& saving = make(); studio.preprocessor = true; @@ -698,14 +866,23 @@ namespace tut nowhere.args = { "x.lsl" }; wrong.problems = { missing, nowhere, anError() }; studio.answerRun(wrong); - ensure_equals("sent: the save keeps the work", studio.sent.size(), size_t(1)); + UNSCOPED_INFO("sent: the save keeps the work"); + CHECK(studio.sent.size() == size_t(1)); const auto going = std::find_if(services.reports.begin(), services.reports.end(), [](const al_studio_test::FakeServices::Said& s) { return alscriptstudiosaving_data::nameOf(s.text) == "SavingWithout"; }); - ensure("said as it went, by name, a failure of the tab", - going != services.reports.end() && going->text.find("[FILES]=lib.lsl, x.lsl") != std::string::npos && going->failure && going->doc == "a"); - ensure("with where includes come from", going->text.find("PreprocessMissingDiskRoute") != std::string::npos); - ensure_equals("and the error besides them", lastSaid(), std::string("SavingWithErrors")); - ensure("counted without them", services.reports.back().text.find("[COUNT]=1") != std::string::npos); + { + INFO("said as it went, by name, a failure of the tab"); + REQUIRE(going != services.reports.end()); + CHECK(going->text.find("[FILES]=lib.lsl, x.lsl") != std::string::npos); + CHECK(going->failure); + CHECK(going->doc == "a"); + } + UNSCOPED_INFO("with where includes come from"); + CHECK(going->text.find("PreprocessMissingDiskRoute") != std::string::npos); + UNSCOPED_INFO("and the error besides them"); + CHECK(lastSaid() == std::string("SavingWithErrors")); + UNSCOPED_INFO("counted without them"); + CHECK(services.reports.back().text.find("[COUNT]=1") != std::string::npos); saving.compiled(answer(doc)); // Only the other errors: those said. @@ -715,7 +892,11 @@ namespace tut plain.text = "default {}"; plain.problems = { anError() }; studio.answerRun(plain); - ensure("sent, its errors said", studio.sent.size() == 2 && lastSaid() == "SavingWithErrors"); + { + INFO("sent, its errors said"); + CHECK(studio.sent.size() == 2); + CHECK(lastSaid() == "SavingWithErrors"); + } saving.compiled(answer(doc)); // One that never came: sent without it, and said. @@ -725,38 +906,51 @@ namespace tut waiting.text = "default {}"; waiting.pending = { "far.lsl" }; studio.answerRun(waiting); - ensure_equals("sent without it", studio.sent.size(), size_t(3)); - ensure("and said so", services.reports.back().text.find("SavingWithout") == 0 && - services.reports.back().text.find("[FILES]=far.lsl") != std::string::npos); + UNSCOPED_INFO("sent without it"); + CHECK(studio.sent.size() == size_t(3)); + { + INFO("and said so"); + CHECK(services.reports.back().text.find("SavingWithout") == 0); + CHECK(services.reports.back().text.find("[FILES]=far.lsl") != std::string::npos); + } } - template<> template<> - void alscriptstudiosaving_object::test<13>() + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: a tab sending takes only its own save's answer and not a recompile's", "[alscriptstudiosaving]") { - set_test_name("a tab sending takes only its own save's answer: another's for the same script, a recompile's, does not land as its own"); ALScriptStudioSaving& saving = make(); Doc& doc = tab("a", "default {}"); type(doc, "\n// mine"); saving.save(doc); - ensure("on its way", doc.save.sending()); + UNSCOPED_INFO("on its way"); + CHECK(doc.save.sending()); const U64 mine = studio.sent.back().options.sender.request; - ensure("sent as a request of the studio's", mine != 0 && studio.sent.back().options.sender.origin == ALScriptOrigin::Studio); + { + INFO("sent as a request of the studio's"); + CHECK(mine != 0); + CHECK(studio.sent.back().options.sender.origin == ALScriptOrigin::Studio); + } // A recompile from the explorer answers first, for the same script. CompileResult other = answer(doc); other.sender = ALScriptSender(ALScriptOrigin::Recompile, mine + 100); saving.compiled(other); - ensure("still on its way", doc.save.sending()); - ensure("not marked saved by another's answer", doc.editor->isDirty()); + UNSCOPED_INFO("still on its way"); + CHECK(doc.save.sending()); + UNSCOPED_INFO("not marked saved by another's answer"); + CHECK(doc.editor->isDirty()); saving.compiled(answer(doc)); - ensure("its own lands", !doc.save.sending() && !doc.editor->isDirty()); + { + INFO("its own lands"); + CHECK_FALSE(doc.save.sending()); + CHECK_FALSE(doc.editor->isDirty()); + } } - template<> template<> - void alscriptstudiosaving_object::test<14>() + // Taken in by a tab with nothing typed, asked about by one with changes, + // let be where it saved what the tab last had. + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: a save from elsewhere is taken in or asked about or let be", "[alscriptstudiosaving]") { - set_test_name("a save from elsewhere: taken in by a tab with nothing typed, asked about by one with changes, let be where it saved what the tab last had"); ALScriptStudioSaving& saving = make(); const auto saved = [](const Doc& doc, const std::string& text, ALScriptOrigin origin, U64 request = 0) { ALScriptSaved one; @@ -770,25 +964,42 @@ namespace tut // Nothing typed here: taken in as loaded. Doc& clean = tab("clean", "default {}"); saving.savedElsewhere(saved(clean, "default { touch_start(integer n) {} }", ALScriptOrigin::Bridge)); - ensure("taken in", studio.loadedAgain == Names{ "clean:default { touch_start(integer n) {} }" }); + UNSCOPED_INFO("taken in"); + CHECK(studio.loadedAgain == Names{ "clean:default { touch_start(integer n) {} }" }); // Changed here and there: asked, and nothing taken yet. Doc& dirty = tab("dirty", "default {}"); type(dirty, "\n// mine"); saving.savedElsewhere(saved(dirty, "default {}\n// theirs", ALScriptOrigin::Bridge)); - ensure_equals("asked whose to keep", lastSaid(), std::string("SavedElsewhereConflict")); - ensure("with the four answers", services.reports.back().actions == Names{ "take_saved", "keep_saved", "merge_saved", "compare_saved" }); - ensure("nothing taken yet", dirty.editor->wholeText() == "default {}\n// mine" && dirty.savedThere); + UNSCOPED_INFO("asked whose to keep"); + CHECK(lastSaid() == std::string("SavedElsewhereConflict")); + UNSCOPED_INFO("with the four answers"); + CHECK(services.reports.back().actions == Names{ "take_saved", "keep_saved", "merge_saved", "compare_saved" }); + { + INFO("nothing taken yet"); + CHECK(dirty.editor->wholeText() == "default {}\n// mine"); + CHECK(dirty.savedThere); + } saving.takeSaved(dirty); - ensure("theirs taken, and nothing to save", dirty.editor->wholeText() == "default {}\n// theirs" && !dirty.editor->isDirty()); - ensure("asked no more", !dirty.savedThere); + { + INFO("theirs taken, and nothing to save"); + CHECK(dirty.editor->wholeText() == "default {}\n// theirs"); + CHECK_FALSE(dirty.editor->isDirty()); + } + UNSCOPED_INFO("asked no more"); + CHECK_FALSE(dirty.savedThere); // A recompile of what the tab last had: nothing changes, nothing asked. Doc& kept = tab("kept", "default {}"); type(kept, "\n// typing"); const size_t said = services.reports.size(); saving.savedElsewhere(saved(kept, "default {}", ALScriptOrigin::Recompile)); - ensure("not asked", services.reports.size() == said && !kept.savedThere && kept.editor->isDirty()); + { + INFO("not asked"); + CHECK(services.reports.size() == said); + CHECK_FALSE(kept.savedThere); + CHECK(kept.editor->isDirty()); + } // In its envelope: read by its source; what went up is what is here. ALScriptEnvelope envelope; @@ -796,7 +1007,11 @@ namespace tut envelope.expanded = "default {}"; envelope.compileTarget = "mono"; saving.savedElsewhere(saved(kept, envelope.wrap(), ALScriptOrigin::Bridge)); - ensure("the same as here: saved", !kept.editor->isDirty() && services.reports.size() == said); + { + INFO("the same as here: saved"); + CHECK_FALSE(kept.editor->isDirty()); + CHECK(services.reports.size() == said); + } // Its own save lands as its answer, not as a save from elsewhere. Doc& own = tab("own", "default {}"); @@ -804,26 +1019,37 @@ namespace tut saving.save(own); ALScriptSaved mine = saved(own, "something else", ALScriptOrigin::Studio, own.save.request()); saving.savedElsewhere(mine); - ensure("its own let be", !own.savedThere && own.editor->wholeText() == "default {} "); + { + INFO("its own let be"); + CHECK_FALSE(own.savedThere); + CHECK(own.editor->wholeText() == "default {} "); + } // Kept: what is here stays, to be saved. type(dirty, "\n// again"); saving.savedElsewhere(saved(dirty, "default {}\n// third", ALScriptOrigin::Editor)); saving.keepSaved(dirty); - ensure("kept", !dirty.savedThere && dirty.editor->isDirty() && lastStatus() == "SavedElsewhereKept"); + { + INFO("kept"); + CHECK_FALSE(dirty.savedThere); + CHECK(dirty.editor->isDirty()); + CHECK(lastStatus() == "SavedElsewhereKept"); + } } - template<> template<> - void alscriptstudiosaving_object::test<15>() + // It asks what the item holds now, and stops with Save Anyway, Reload and + // Compare. + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: a save stops where the item was saved elsewhere since until asked again", "[alscriptstudiosaving]") { - set_test_name("a save asks what the item holds now; saved elsewhere since, it stops with Save Anyway, Reload and Compare, and goes when asked again"); ALScriptStudioSaving& saving = make(); Doc& doc = tab("a", "default {}"); doc.assetId.generate(); type(doc, " "); saving.save(doc); - ensure_equals("asked once", studio.worldAsks, 1); - ensure_equals("nothing moved: sent", studio.sent.size(), size_t(1)); + UNSCOPED_INFO("asked once"); + CHECK(studio.worldAsks == 1); + UNSCOPED_INFO("nothing moved: sent"); + CHECK(studio.sent.size() == size_t(1)); saving.compiled(answer(doc)); LLUUID elsewhere; @@ -831,17 +1057,24 @@ namespace tut studio.worldHolds = elsewhere; type(doc, "x"); saving.save(doc); - ensure_equals("moved: said", lastSaid(), std::string("SaveWorldMoved")); - ensure("with what can be done", services.reports.back().actions == Names{ "save_anyway", "reload_world", "merge_world", "compare_world" }); - ensure("and not sent", studio.sent.size() == 1 && !doc.save.underway()); + UNSCOPED_INFO("moved: said"); + CHECK(lastSaid() == std::string("SaveWorldMoved")); + UNSCOPED_INFO("with what can be done"); + CHECK(services.reports.back().actions == Names{ "save_anyway", "reload_world", "merge_world", "compare_world" }); + { + INFO("and not sent"); + CHECK(studio.sent.size() == 1); + CHECK_FALSE(doc.save.underway()); + } saving.saveAsked(doc); - ensure_equals("asked again: sent over it", studio.sent.size(), size_t(2)); + UNSCOPED_INFO("asked again: sent over it"); + CHECK(studio.sent.size() == size_t(2)); } - template<> template<> - void alscriptstudiosaving_object::test<16>() + // As every editor of the item decides it: the same text, taken, kept or + // asked about. + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: heard sorts a save from elsewhere and reads the last save only if needed", "[alscriptstudiosaving]") { - set_test_name("a save heard from elsewhere, as every editor of the item decides it: the same text, taken, kept or asked about; what was last saved asked only where it is needed"); using Heard = ALScriptSaved::Heard; S32 asked = 0; auto last_was = [&asked](std::optional text) { @@ -850,19 +1083,25 @@ namespace tut return text; }; }; - ensure("the same text", ALScriptSaved::heard("a", "a", true, last_was("b")) == Heard::Same); - ensure("nothing typed: taken", ALScriptSaved::heard("a", "b", false, last_was("b")) == Heard::Take); - ensure_equals("without asking what was last saved", asked, 0); - ensure("typed, and theirs is what was last saved: kept", ALScriptSaved::heard("a", "b", true, last_was("a")) == Heard::Keep); - ensure("typed, and theirs is new: asked", ALScriptSaved::heard("a", "b", true, last_was("c")) == Heard::Ask); - ensure("typed, nothing known of the last save: asked", ALScriptSaved::heard("a", "b", true, last_was(std::nullopt)) == Heard::Ask); - ensure_equals("asked where it was needed", asked, 3); + UNSCOPED_INFO("the same text"); + CHECK(ALScriptSaved::heard("a", "a", true, last_was("b")) == Heard::Same); + UNSCOPED_INFO("nothing typed: taken"); + CHECK(ALScriptSaved::heard("a", "b", false, last_was("b")) == Heard::Take); + UNSCOPED_INFO("without asking what was last saved"); + CHECK(asked == 0); + UNSCOPED_INFO("typed, and theirs is what was last saved: kept"); + CHECK(ALScriptSaved::heard("a", "b", true, last_was("a")) == Heard::Keep); + UNSCOPED_INFO("typed, and theirs is new: asked"); + CHECK(ALScriptSaved::heard("a", "b", true, last_was("c")) == Heard::Ask); + UNSCOPED_INFO("typed, nothing known of the last save: asked"); + CHECK(ALScriptSaved::heard("a", "b", true, last_was(std::nullopt)) == Heard::Ask); + UNSCOPED_INFO("asked where it was needed"); + CHECK(asked == 3); } - template<> template<> - void alscriptstudiosaving_object::test<17>() + // A save sent before it leaves it to be undone. + TEST_CASE_METHOD(alscriptstudiosaving_data, "alscriptstudiosaving: a merge is kept for good once a save carrying it is saved", "[alscriptstudiosaving]") { - set_test_name("a merge is kept for good once a save that carries it is saved; a save sent before it leaves it to be undone"); ALScriptStudioSaving& saving = make(); // As ALScriptStudioMerging keeps a merge just put in: the step it // made, and what it set aside. @@ -878,7 +1117,8 @@ namespace tut merged(doc); saving.save(doc); saving.compiled(answer(doc)); - ensure("saved with it: kept for good", !doc.merged); + UNSCOPED_INFO("saved with it: kept for good"); + CHECK_FALSE(doc.merged); Doc& early = tab("b", "default {}"); type(early, "\n// typed"); @@ -886,6 +1126,10 @@ namespace tut type(early, "\n// merged while it went"); merged(early); saving.compiled(answer(early)); - ensure("sent before it: still to be undone", early.merged && early.merged->savedThere == std::string("theirs")); + { + INFO("sent before it: still to be undone"); + REQUIRE(early.merged); + CHECK(early.merged->savedThere == std::string("theirs")); + } } } diff --git a/indra/newview/tests/alscriptstudioselections_test.cpp b/indra/newview/tests/alscriptstudioselections_test.cpp index 806aba908d..d2450aa493 100644 --- a/indra/newview/tests/alscriptstudioselections_test.cpp +++ b/indra/newview/tests/alscriptstudioselections_test.cpp @@ -29,7 +29,7 @@ #include "aldiffview.h" #include "alscriptstudio_fixture.h" -#include "../test/lltut.h" +#include namespace { @@ -72,7 +72,7 @@ namespace }; } -namespace tut +namespace { struct alscriptstudioselections_data { @@ -136,70 +136,78 @@ namespace tut } }; - typedef test_group alscriptstudioselections_group; - typedef alscriptstudioselections_group::object alscriptstudioselections_object; - tut::alscriptstudioselections_group alscriptstudioselections_test("alscriptstudioselections"); - - template<> template<> - void alscriptstudioselections_object::test<1>() + TEST_CASE_METHOD(alscriptstudioselections_data, "alscriptstudioselections: hold keeps a selection titled by its tab and lines and says so", "[alscriptstudioselections]") { - set_test_name("nothing selected, nothing held; a selection held by its tab's name and its lines, and said"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& door = tab("door", lines(8)); - ensure("nothing to hold", !ALScriptStudioSelections::canHold(door)); + UNSCOPED_INFO("nothing to hold"); + CHECK_FALSE(ALScriptStudioSelections::canHold(door)); unit->hold(door); - ensure("none held", !unit->held()); + UNSCOPED_INFO("none held"); + REQUIRE_FALSE(unit->held()); door.editor->goTo(ALTextRange(ALTextPos(4, 0), ALTextPos(2, 0))); - ensure("something to hold", ALScriptStudioSelections::canHold(door)); + UNSCOPED_INFO("something to hold"); + CHECK(ALScriptStudioSelections::canHold(door)); unit->hold(door); - ensure("held", unit->held().has_value()); - ensure_equals("its text, whichever way it was selected", unit->held()->text, std::string("line 2\nline 3\n")); - ensure_equals("its lines, the last ending at a line's start not counted", unit->held()->title, titled("SelectionLines", "door", 3, 4)); + REQUIRE(unit->held().has_value()); + UNSCOPED_INFO("its text, whichever way it was selected"); + CHECK(unit->held()->text == std::string("line 2\nline 3\n")); + UNSCOPED_INFO("its lines, the last ending at a line's start not counted"); + CHECK(unit->held()->title == titled("SelectionLines", "door", 3, 4)); LLStringUtil::format_map_t args; args["[TITLE]"] = unit->held()->title; - ensure_equals("said", services().statuses.back(), said("SelectionHeld", args)); + CHECK(services().statuses.back() == said("SelectionHeld", args)); door.editor->goTo(ALTextRange(ALTextPos(5, 0), ALTextPos(5, 4))); unit->hold(door); - ensure("one line, in place of the other", unit->held()->text == "line" && unit->held()->title == titled("SelectionLine", "door", 6)); + { + INFO("one line, in place of the other"); + REQUIRE(unit->held()->text == "line"); + CHECK(unit->held()->title == titled("SelectionLine", "door", 6)); + } } - template<> template<> - void alscriptstudioselections_object::test<2>() + TEST_CASE_METHOD(alscriptstudioselections_data, "alscriptstudioselections: compare puts the held left of another tab's selection in its place", "[alscriptstudioselections]") { - set_test_name("the held compared with another tab's selection, in that tab's place: the held on the left, each under its title"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& door = tab("door", lines(8)); Doc& lamp = tab("lamp", lines(8, { { 3, "lamp 3" } })); lamp.editor->goTo(ALTextRange(ALTextPos(2, 0), ALTextPos(4, 0))); - ensure("nothing held: nothing to compare", !unit->canCompare(lamp)); + UNSCOPED_INFO("nothing held: nothing to compare"); + CHECK_FALSE(unit->canCompare(lamp)); door.editor->goTo(ALTextRange(ALTextPos(2, 0), ALTextPos(4, 0))); unit->hold(door); - ensure("held, and lamp's selected", unit->canCompare(lamp)); + UNSCOPED_INFO("held, and lamp's selected"); + CHECK(unit->canCompare(lamp)); lamp.editor->goTo(ALTextPos(0, 0)); - ensure("nothing selected in lamp", !unit->canCompare(lamp)); + UNSCOPED_INFO("nothing selected in lamp"); + CHECK_FALSE(unit->canCompare(lamp)); lamp.editor->goTo(ALTextRange(ALTextPos(2, 0), ALTextPos(4, 0))); unit->compare(lamp); - ensure_equals("compared", studio.did.size(), 1U); - ensure_equals("held against lamp's", studio.did[0], + UNSCOPED_INFO("compared"); + REQUIRE(studio.did.size() == 1U); + UNSCOPED_INFO("held against lamp's"); + CHECK(studio.did[0] == "line 2\nline 3\n | line 2\nlamp 3\n (" + titled("SelectionLines", "door", 3, 4) + " | " + titled("SelectionLines", "lamp", 3, 4) + ")"); - ensure("in lamp's place", lamp.compareView && lamp.compareView->changeCount() == 1); + { + INFO("in lamp's place"); + REQUIRE(lamp.compareView); + CHECK(lamp.compareView->changeCount() == 1); + } } - template<> template<> - void alscriptstudioselections_object::test<3>() + TEST_CASE_METHOD(alscriptstudioselections_data, "alscriptstudioselections: typing and taking back edit the source's stretch until it moves", "[alscriptstudioselections]") { - set_test_name("in the source: typing goes on at the selection's place, a change taken back edits that stretch; once the tab moves, neither"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& door = tab("door", lines(10)); door.editor->goTo(ALTextRange(ALTextPos(1, 0), ALTextPos(3, 0))); @@ -207,22 +215,33 @@ namespace tut door.editor->goTo(ALTextRange(ALTextPos(5, 0), ALTextPos(7, 0))); unit->compare(door); ALDiffView& view = *door.compareView; - ensure("its changes may be taken back", view.canTakeBack()); + UNSCOPED_INFO("its changes may be taken back"); + CHECK(view.canTakeBack()); // Typed on the comparison's second line, two in: the source's // seventh line, two in. view.right()->setFocus(true); view.right()->goTo(ALTextPos(1, 2)); view.right()->handleUnicodeChar('x', false); - ensure("sent to the stretch's place", studio.typedAt.size() == 1 && studio.typedAt[0] == ALTextPos(6, 2)); - ensure_equals("typed there", door.editor->text(), lines(10, { { 6, "lixne 6" } })); + { + INFO("sent to the stretch's place"); + REQUIRE(studio.typedAt.size() == 1); + CHECK(studio.typedAt[0] == ALTextPos(6, 2)); + } + UNSCOPED_INFO("typed there"); + CHECK(door.editor->text() == lines(10, { { 6, "lixne 6" } })); // Compared again, a change taken back: the stretch made the held's. door.editor->goTo(ALTextRange(ALTextPos(5, 0), ALTextPos(7, 0))); unit->compare(door); - ensure("taken back", view.takeBack(0)); - ensure_equals("the stretch made the held's", door.editor->text(), lines(10, { { 5, "line 1" }, { 6, "line 2" } })); - ensure("the comparison with it", view.rightText() == "line 1\nline 2\n" && view.changeCount() == 0); + CHECK(view.takeBack(0)); + UNSCOPED_INFO("the stretch made the held's"); + CHECK(door.editor->text() == lines(10, { { 5, "line 1" }, { 6, "line 2" } })); + { + INFO("the comparison with it"); + CHECK(view.rightText() == "line 1\nline 2\n"); + CHECK(view.changeCount() == 0); + } // A stretch whose lines change in number: the next taken back // still lands within it. @@ -230,8 +249,9 @@ namespace tut unit->hold(door); door.editor->goTo(ALTextRange(ALTextPos(5, 0), ALTextPos(7, 0))); unit->compare(door); - ensure("two lines made one", view.takeBack(0)); - ensure_equals("the source", door.editor->text(), lines(9, { { 5, "line 0" }, { 6, "line 7" }, { 7, "line 8" }, { 8, "line 9" } })); + UNSCOPED_INFO("two lines made one"); + CHECK(view.takeBack(0)); + CHECK(door.editor->text() == lines(9, { { 5, "line 0" }, { 6, "line 7" }, { 7, "line 8" }, { 8, "line 9" } })); // The source moved under a comparison: nothing taken back, typing // at the stretch's start. @@ -240,20 +260,23 @@ namespace tut door.editor->goTo(ALTextPos(0, 0)); door.editor->insertText("new\n"); const std::string moved = door.editor->text(); - ensure("nothing taken back", !view.takeBack(0) && door.editor->text() == moved); + { + INFO("nothing taken back"); + CHECK_FALSE(view.takeBack(0)); + CHECK(door.editor->text() == moved); + } view.right()->setFocus(true); view.right()->goTo(ALTextPos(1, 3)); view.right()->handleUnicodeChar('y', false); - ensure("typing at the stretch's start", studio.typedAt.back() == ALTextPos(1, 0)); + UNSCOPED_INFO("typing at the stretch's start"); + CHECK(studio.typedAt.back() == ALTextPos(1, 0)); } - template<> template<> - void alscriptstudioselections_object::test<4>() + TEST_CASE_METHOD(alscriptstudioselections_data, "alscriptstudioselections: a selection in the preprocessed view compares as text and edits nothing", "[alscriptstudioselections]") { - set_test_name("a selection in what is not the source -- the preprocessed view -- compared as text alone: nothing taken back, and nothing typed into the script"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& door = tab("door", lines(8)); door.editor->goTo(ALTextRange(ALTextPos(0, 0), ALTextPos(2, 0))); @@ -261,24 +284,29 @@ namespace tut door.expandedEditor = editor("expanded_door", lines(8, { { 1, "expanded 1" } })); door.view = Doc::View::Expanded; door.expandedEditor->goTo(ALTextRange(ALTextPos(0, 0), ALTextPos(2, 0))); - ensure("held from the view in sight", ALScriptStudioSelections::canHold(door) && unit->canCompare(door)); + { + INFO("held from the view in sight"); + CHECK(ALScriptStudioSelections::canHold(door)); + CHECK(unit->canCompare(door)); + } unit->compare(door); - ensure_equals("its text", studio.did.back(), + UNSCOPED_INFO("its text"); + CHECK(studio.did.back() == "line 0\nline 1\n | line 0\nexpanded 1\n (" + titled("SelectionLines", "door", 1, 2) + " | " + titled("SelectionLines", "door", 1, 2) + ")"); - ensure("nothing taken back", !door.compareView->canTakeBack()); + UNSCOPED_INFO("nothing taken back"); + CHECK_FALSE(door.compareView->canTakeBack()); door.compareView->right()->setFocus(true); door.compareView->right()->goTo(ALTextPos(1, 2)); door.compareView->right()->handleUnicodeChar('z', false); - ensure("nothing typed into the script: the expansion is not it", studio.typedAt.empty()); + UNSCOPED_INFO("nothing typed into the script: the expansion is not it"); + REQUIRE(studio.typedAt.empty()); } - template<> template<> - void alscriptstudioselections_object::test<5>() + TEST_CASE_METHOD(alscriptstudioselections_data, "alscriptstudioselections: typing maps a mid-line selection's first line from where it starts", "[alscriptstudioselections]") { - set_test_name("a selection starting within a line: its first line's places counted from where it starts, the rest from their lines' starts"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } Doc& door = tab("door", "integer a = alpha;\ninteger b = beta;\ninteger c = 1;"); door.editor->goTo(ALTextRange(ALTextPos(0, 12), ALTextPos(0, 17))); @@ -286,18 +314,22 @@ namespace tut door.editor->goTo(ALTextRange(ALTextPos(1, 12), ALTextPos(2, 9))); unit->compare(door); ALDiffView& view = *door.compareView; - ensure_equals("the stretch", view.rightText(), std::string("beta;\ninteger c")); + UNSCOPED_INFO("the stretch"); + CHECK(view.rightText() == std::string("beta;\ninteger c")); view.right()->setFocus(true); view.right()->goTo(ALTextPos(0, 2)); view.right()->handleUnicodeChar('x', false); - ensure("on its first line, from where it starts", studio.typedAt.back() == ALTextPos(1, 14)); - ensure_equals("typed there", door.editor->text(), std::string("integer a = alpha;\ninteger b = bexta;\ninteger c = 1;")); + UNSCOPED_INFO("on its first line, from where it starts"); + CHECK(studio.typedAt.back() == ALTextPos(1, 14)); + UNSCOPED_INFO("typed there"); + CHECK(door.editor->text() == std::string("integer a = alpha;\ninteger b = bexta;\ninteger c = 1;")); door.editor->goTo(ALTextRange(ALTextPos(1, 12), ALTextPos(2, 9))); unit->compare(door); view.right()->setFocus(true); view.right()->goTo(ALTextPos(1, 3)); view.right()->handleUnicodeChar('y', false); - ensure("on a line after, from its start", studio.typedAt.back() == ALTextPos(2, 3)); + UNSCOPED_INFO("on a line after, from its start"); + CHECK(studio.typedAt.back() == ALTextPos(2, 3)); } } diff --git a/indra/newview/tests/alscriptstudiovim_test.cpp b/indra/newview/tests/alscriptstudiovim_test.cpp index c99588e825..b37ba2a8e2 100644 --- a/indra/newview/tests/alscriptstudiovim_test.cpp +++ b/indra/newview/tests/alscriptstudiovim_test.cpp @@ -31,7 +31,7 @@ #include "fsyspath.h" -#include "../test/lltut.h" +#include #include #include @@ -160,7 +160,7 @@ namespace }; } -namespace tut +namespace { struct alscriptstudiovim_data { @@ -221,7 +221,7 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } for (const char* name : { "save", "close", "save_all", "format", "quick_fix", "fix_all", "back", "forward", "go_to_definition", "reference", "go_to_symbol", "blanks_all", "blanks_none" }) @@ -333,163 +333,234 @@ namespace tut } }; - typedef test_group alscriptstudiovim_group; - typedef alscriptstudiovim_group::object alscriptstudiovim_object; - alscriptstudiovim_group alscriptstudiovim_instance("alscriptstudiovim"); - - template<> template<> - void alscriptstudiovim_object::test<1>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: each : command reaches the menus' table or the window", "[alscriptstudiovim]") { - set_test_name("each : command reaches its command: the menus' through the window's table, the rest through the window"); ALScriptStudioVim& vim = make(); Doc& a = tab("a"); tab("b"); for (const char* name : { "w", "write", "w!" }) { - ensure(name, ex(a, name)); + CAPTURE(name); + REQUIRE(ex(a, name)); } - ensure("written by the menus' Save", ran == Names{ "save", "save", "save" }); + UNSCOPED_INFO("written by the menus' Save"); + CHECK(ran == Names{ "save", "save", "save" }); ran.clear(); - ensure("q", ex(a, "q") && ex(a, "wa") && ex(a, "format") && ex(a, "fix") && ex(a, "fixall") && ex(a, "problems")); - ensure("each by its menu name", ran == Names{ "close", "save_all", "format", "quick_fix", "fix_all", "problems" }); + CHECK(ex(a, "q")); + CHECK(ex(a, "wa")); + CHECK(ex(a, "format")); + CHECK(ex(a, "fix")); + CHECK(ex(a, "fixall")); + CHECK(ex(a, "problems")); + UNSCOPED_INFO("each by its menu name"); + CHECK(ran == Names{ "close", "save_all", "format", "quick_fix", "fix_all", "problems" }); ran.clear(); - ensure("one that cannot be done now is taken, and not done", ex(a, "revert") && ran.empty()); + { + INFO("one that cannot be done now is taken, and not done"); + CHECK(ex(a, "revert")); + CHECK(ran.empty()); + } - ensure("q! lets it go as it stands", ex(a, "q!") && studio.letGo == Names{ "a" }); - ensure("wq over nothing unsaved closes it", ex(a, "wq") && studio.asked == Names{ "a" } && studio.savedToClose.empty()); + { + INFO("q! lets it go as it stands"); + CHECK(ex(a, "q!")); + CHECK(studio.letGo == Names{ "a" }); + } + { + INFO("wq over nothing unsaved closes it"); + CHECK(ex(a, "wq")); + CHECK(studio.asked == Names{ "a" }); + CHECK(studio.savedToClose.empty()); + } a.editor->setCaret(a.editor->document().end()); a.editor->insertText(" "); - ensure("wq over something unsaved saves it to close", ex(a, "x") && studio.savedToClose == Names{ "a" }); - ensure("qa closes them all, asking once", ex(a, "qa") && studio.many == Names{ "a", "b" }); + { + INFO("wq over something unsaved saves it to close"); + CHECK(ex(a, "x")); + CHECK(studio.savedToClose == Names{ "a" }); + } + { + INFO("qa closes them all, asking once"); + CHECK(ex(a, "qa")); + CHECK(studio.many == Names{ "a", "b" }); + } studio.letGo.clear(); - ensure("qa! lets them all go", ex(a, "qa!") && studio.letGo == Names{ "a", "b" }); + { + INFO("qa! lets them all go"); + CHECK(ex(a, "qa!")); + CHECK(studio.letGo == Names{ "a", "b" }); + } - ensure("one it does not know is vim's to say", !ex(a, "frobnicate")); + UNSCOPED_INFO("one it does not know is vim's to say"); + CHECK_FALSE(ex(a, "frobnicate")); ALCodeEditor* stray = editor("stray", "x"); - ensure("nor from an editor of no tab", !vim.command(*stray, "w", std::string()) && ran.empty()); + { + INFO("nor from an editor of no tab"); + CHECK_FALSE(vim.command(*stray, "w", std::string())); + CHECK(ran.empty()); + } } - template<> template<> - void alscriptstudiovim_object::test<2>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: :set toggles number and relativenumber only when they differ", "[alscriptstudiovim]") { - set_test_name(":set turns number and relativenumber on or off through their toggles, only where they are not so already"); make(); Doc& a = tab("a"); - ensure("on", ex(a, "set", "number") && lineNumbers && ran == Names{ "line_numbers" }); - ensure("on already: left", ex(a, "set", "nu") && ran.size() == 1); - ensure("off", ex(a, "set", "nonu") && !lineNumbers && ran.size() == 2); - ensure("relative", ex(a, "set", "rnu") && relativeNumbers); - ensure("off already: left", ex(a, "set", "norelativenumber") && !relativeNumbers && ran.size() == 4); - ensure("an option it does not know is vim's", !ex(a, "set", "hlsearch")); + CHECK(ex(a, "set", "number")); + CHECK(lineNumbers); + CHECK(ran == Names{ "line_numbers" }); + { + INFO("on already: left"); + CHECK(ex(a, "set", "nu")); + CHECK(ran.size() == 1); + } + CHECK(ex(a, "set", "nonu")); + CHECK_FALSE(lineNumbers); + CHECK(ran.size() == 2); + CHECK(ex(a, "set", "rnu")); + CHECK(relativeNumbers); + { + INFO("off already: left"); + CHECK(ex(a, "set", "norelativenumber")); + CHECK_FALSE(relativeNumbers); + CHECK(ran.size() == 4); + } + UNSCOPED_INFO("an option it does not know is vim's"); + CHECK_FALSE(ex(a, "set", "hlsearch")); } - template<> template<> - void alscriptstudiovim_object::test<3>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: :history lists command or search lines or both in Output", "[alscriptstudiovim]") { - set_test_name(":history lists the lines entered in the Output tab: the : ones, the searches, or both"); ALScriptStudioVim& vim = make(); Doc& a = tab("a"); vim.shared().command = { "w", "set nu" }; vim.shared().search = { "foo" }; - ensure("the : ones", ex(a, "history")); - ensure_equals("one entry", studio.entries.size(), size_t(1)); - ensure_equals("numbered", studio.entries[0].text, std::string("cmd history:\n 1 w\n 2 set nu")); - ensure_equals("from vim", studio.entries[0].source, std::string("OutputSourceVim")); - ensure("the tab in sight", studio.outputShown == 1); + UNSCOPED_INFO("the : ones"); + CHECK(ex(a, "history")); + REQUIRE(studio.entries.size() == size_t(1)); + CHECK(studio.entries[0].text == std::string("cmd history:\n 1 w\n 2 set nu")); + CHECK(studio.entries[0].source == std::string("OutputSourceVim")); + UNSCOPED_INFO("the tab in sight"); + CHECK(studio.outputShown == 1); studio.entries.clear(); - ensure("the searches", ex(a, "his", "/") && studio.entries.size() == 1 && studio.entries[0].text == "search history:\n 1 foo"); + { + INFO("the searches"); + CHECK(ex(a, "his", "/")); + REQUIRE(studio.entries.size() == 1); + CHECK(studio.entries[0].text == "search history:\n 1 foo"); + } studio.entries.clear(); - ensure("both", ex(a, "history", "all") && studio.entries.size() == 2); + CHECK(ex(a, "history", "all")); + CHECK(studio.entries.size() == 2); } - template<> template<> - void alscriptstudiovim_object::test<4>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: q: lists lines last first to run or to put back on the line", "[alscriptstudiovim]") { - set_test_name("q: lists the lines entered, the last first; Return runs one, Shift-Return puts it back on the line"); ALScriptStudioVim& vim = make(); Doc& a = tab("a"); std::vector> chosen; vim.historyWindow(*a.editor, ':', { "first", "second", "third" }, [&chosen](const std::string& line, bool run) { chosen.emplace_back(line, run); }); - ensure("the last first", studio.offered == Names{ "third", "second", "first" }); - ensure_equals("as tall as they are", studio.pickRows, 3); - ensure_equals("asking of the kind", studio.asking, std::string("VimHistoryPlaceholder [KIND]=:")); + UNSCOPED_INFO("the last first"); + CHECK(studio.offered == Names{ "third", "second", "first" }); + UNSCOPED_INFO("as tall as they are"); + CHECK(studio.pickRows == 3); + UNSCOPED_INFO("asking of the kind"); + CHECK(studio.asking == std::string("VimHistoryPlaceholder [KIND]=:")); studio.pick("second"); studio.shift("first"); - ensure("run, and put back", chosen == std::vector>{ { "second", true }, { "first", false } }); - ensure("none picked is nothing chosen", (studio.cancel(), chosen.size() == 2)); + UNSCOPED_INFO("run, and put back"); + CHECK(chosen == std::vector>{ { "second", true }, { "first", false } }); + UNSCOPED_INFO("none picked is nothing chosen"); + CHECK((studio.cancel(), chosen.size() == 2)); vim.historyWindow(*a.editor, '/', Names(20, "x"), [](const std::string&, bool) {}); - ensure_equals("no taller than eight", studio.pickRows, 8); + UNSCOPED_INFO("no taller than eight"); + CHECK(studio.pickRows == 8); vim.historyWindow(*a.editor, '/', Names(), [](const std::string&, bool) {}); - ensure_equals("nor shorter than one", studio.pickRows, 1); + UNSCOPED_INFO("nor shorter than one"); + CHECK(studio.pickRows == 1); } - template<> template<> - void alscriptstudiovim_object::test<5>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: = formats the given lines of a changeable script's source only", "[alscriptstudiovim]") { - set_test_name("= formats the lines given of the source; not of the expansion, a notecard or a tab that cannot change"); ALScriptStudioVim& vim = make(); Doc& a = tab("a", "one\ntwo\nthree\nfour"); vim.format(*a.editor, 1, 2); - ensure("formatted, as a selection", studio.formatted == Names{ "a selection" }); + UNSCOPED_INFO("formatted, as a selection"); + CHECK(studio.formatted == Names{ "a selection" }); const ALTextDocument& text = a.editor->document(); - ensure("of those lines", a.editor->selection() == ALTextRange(text.lineStart(1), text.lineEnd(2))); + UNSCOPED_INFO("of those lines"); + CHECK(a.editor->selection() == ALTextRange(text.lineStart(1), text.lineEnd(2))); vim.format(*a.editor, 2, 99); - ensure("to the last there is", a.editor->selection() == ALTextRange(text.lineStart(2), text.lineEnd(3))); + UNSCOPED_INFO("to the last there is"); + CHECK(a.editor->selection() == ALTextRange(text.lineStart(2), text.lineEnd(3))); a.expandedEditor = editor("expanded_a", "one"); vim.format(*a.expandedEditor, 0, 0); - ensure("not the expansion", studio.formatted.size() == 2); + UNSCOPED_INFO("not the expansion"); + CHECK(studio.formatted.size() == 2); Doc& card = tab("card"); card.notecard = true; vim.format(*card.editor, 0, 0); Doc& locked = tab("locked"); locked.modifiable = false; vim.format(*locked.editor, 0, 0); - ensure("nor a notecard, nor one that cannot change", studio.formatted.size() == 2); - ensure("the expansion's editor is its tab's", vim.docOf(*a.expandedEditor) == &a); + UNSCOPED_INFO("nor a notecard, nor one that cannot change"); + CHECK(studio.formatted.size() == 2); + UNSCOPED_INFO("the expansion's editor is its tab's"); + CHECK(vim.docOf(*a.expandedEditor) == &a); } - template<> template<> - void alscriptstudiovim_object::test<6>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: a connected keymap shares the window's history and shows the banner", "[alscriptstudiovim]") { - set_test_name("a keymap connected shares the window's history and asks it; the banner says vim where the tab in front has it"); ALScriptStudioVim& vim = make(); Doc& a = tab("a"); vim.pump(); - ensure("no vim, nothing said", vim.banner().empty() && studio.trailers == 0); + { + INFO("no vim, nothing said"); + CHECK(vim.banner().empty()); + CHECK(studio.trailers == 0); + } auto keymap = std::make_unique(); vim.connect(*keymap); ALVimKeymap& connected = *keymap; a.editor->setModalKeymap(std::move(keymap)); - ensure("its history the window's", connected.sharedState().get() == &vim.shared()); - ensure("a : command it leaves reaches the table", - connected.hooks().command(*a.editor, "w", std::string()) && ran == Names{ "save" }); + UNSCOPED_INFO("its history the window's"); + CHECK(connected.sharedState().get() == &vim.shared()); + { + INFO("a : command it leaves reaches the table"); + CHECK(connected.hooks().command(*a.editor, "w", std::string())); + CHECK(ran == Names{ "save" }); + } std::vector words; connected.hooks().complete(*a.editor, std::string(), std::string(), words); - ensure("Tab completes the studio's names", std::find(words.begin(), words.end(), "fixall") != words.end()); + UNSCOPED_INFO("Tab completes the studio's names"); + CHECK(std::find(words.begin(), words.end(), "fixall") != words.end()); vim.pump(); - ensure_equals("vim, said", vim.banner(), std::string("VimNormal")); - ensure("the strip told", studio.trailers == 1); + CHECK(vim.banner() == std::string("VimNormal")); + UNSCOPED_INFO("the strip told"); + CHECK(studio.trailers == 1); vim.pump(); - ensure("once", studio.trailers == 1); + UNSCOPED_INFO("told once"); + CHECK(studio.trailers == 1); // Gone, the keymap's hooks do nothing. this->vim.reset(); - ensure("asked of nothing", !connected.hooks().command(*a.editor, "w", std::string()) && ran.size() == 1); + { + INFO("asked of nothing"); + CHECK_FALSE(connected.hooks().command(*a.editor, "w", std::string())); + CHECK(ran.size() == 1); + } } - template<> template<> - void alscriptstudiovim_object::test<7>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: Tab completes commands and their options and kinds and tab names", "[alscriptstudiovim]") { - set_test_name("Tab completes the command names, :set's options, :history's kinds and the tabs' names"); ALScriptStudioVim& vim = make(); tab("alpha"); tab("beta"); @@ -499,84 +570,108 @@ namespace tut vim.complete("his", std::string(), kinds); vim.complete("bd", std::string(), tabs); vim.complete("q", std::string(), none); - ensure("vim's own and the menus'", std::find(names.begin(), names.end(), "wall") != names.end() && - std::find(names.begin(), names.end(), "go_to_line") != names.end()); - ensure("the options", - options == Names{ "number", "nonumber", "relativenumber", "norelativenumber", "spell", "nospell", "list", "nolist" }); - ensure("the kinds", kinds == Names{ "all", "cmd", "search" }); - ensure("the tabs", tabs == Names{ "alpha", "beta" }); - ensure("nothing after anything else", none.empty()); + { + INFO("vim's own and the menus'"); + CHECK(std::find(names.begin(), names.end(), "wall") != names.end()); + CHECK(std::find(names.begin(), names.end(), "go_to_line") != names.end()); + } + CHECK(options == Names{ "number", "nonumber", "relativenumber", "norelativenumber", "spell", "nospell", "list", "nolist" }); + CHECK(kinds == Names{ "all", "cmd", "search" }); + CHECK(tabs == Names{ "alpha", "beta" }); + UNSCOPED_INFO("nothing after anything else"); + CHECK(none.empty()); } - template<> template<> - void alscriptstudiovim_object::test<8>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: :b and :tab commands move between tabs round the strip", "[alscriptstudiovim]") { - set_test_name("the tabs are vim's buffers and tab pages: :bn and :bp round the strip, :b by number, name or #, first and last"); make(); ALVimKeymap* vim = fourTabs(); - ensure_equals(":bn", go("bn"), std::string("beta")); - ensure_equals(":bnext 2", go("bnext", "2"), std::string("delta")); - ensure_equals("round past the end", go("bn"), std::string("alpha")); - ensure_equals(":bp round past the start", go("bp"), std::string("delta")); - ensure_equals(":bN 3", go("bN", "3"), std::string("alpha")); - ensure_equals(":b 3", go("b", "3"), std::string("gamma")); - ensure_equals(":b # the one before", go("b", "#"), std::string("alpha")); - ensure_equals("and back", go("buffer", "#"), std::string("gamma")); - ensure_equals(":b by a part of its name, in any case", go("b", "ELT"), std::string("delta")); - ensure_equals(":b by its whole name", go("b", "beta"), std::string("beta")); - ensure_equals(":bf", go("bf"), std::string("alpha")); - ensure_equals(":blast", go("blast"), std::string("delta")); - ensure_equals(":tabfirst", go("tabfirst"), std::string("alpha")); - ensure_equals(":tabl", go("tabl"), std::string("delta")); - ensure_equals(":tabn round past the end", go("tabn"), std::string("alpha")); - ensure_equals(":tabn 3 the third", go("tabn", "3"), std::string("gamma")); - ensure_equals(":tabn +1", go("tabnext", "+1"), std::string("delta")); - ensure_equals(":tabn $", go("tabn", "$"), std::string("delta")); - ensure_equals(":tabp 2", go("tabp", "2"), std::string("beta")); - ensure_equals(":tabN round past the start", go("tabN", "2"), std::string("delta")); - - ensure_equals(":b a -- in every name", go("b", "a"), std::string("delta")); - ensure_equals("said as vim says it", vim->message(), std::string("VimTabsMatch [NAME]=a")); - ensure("an error", vim->messageIsError()); + CHECK(go("bn") == std::string("beta")); + CHECK(go("bnext", "2") == std::string("delta")); + UNSCOPED_INFO("round past the end"); + CHECK(go("bn") == std::string("alpha")); + UNSCOPED_INFO(":bp round past the start"); + CHECK(go("bp") == std::string("delta")); + CHECK(go("bN", "3") == std::string("alpha")); + CHECK(go("b", "3") == std::string("gamma")); + UNSCOPED_INFO(":b # the one before"); + CHECK(go("b", "#") == std::string("alpha")); + UNSCOPED_INFO("and back"); + CHECK(go("buffer", "#") == std::string("gamma")); + UNSCOPED_INFO(":b by a part of its name, in any case"); + CHECK(go("b", "ELT") == std::string("delta")); + CHECK(go("b", "beta") == std::string("beta")); + CHECK(go("bf") == std::string("alpha")); + CHECK(go("blast") == std::string("delta")); + CHECK(go("tabfirst") == std::string("alpha")); + CHECK(go("tabl") == std::string("delta")); + UNSCOPED_INFO(":tabn round past the end"); + CHECK(go("tabn") == std::string("alpha")); + CHECK(go("tabn", "3") == std::string("gamma")); + CHECK(go("tabnext", "+1") == std::string("delta")); + CHECK(go("tabn", "$") == std::string("delta")); + CHECK(go("tabp", "2") == std::string("beta")); + UNSCOPED_INFO(":tabN round past the start"); + CHECK(go("tabN", "2") == std::string("delta")); + + UNSCOPED_INFO(":b a -- in every name"); + CHECK(go("b", "a") == std::string("delta")); + UNSCOPED_INFO("said as vim says it"); + CHECK(vim->message() == std::string("VimTabsMatch [NAME]=a")); + CHECK(vim->messageIsError()); go("b", "zeta"); - ensure_equals("none", vim->message(), std::string("VimNoTabMatch [NAME]=zeta")); + CHECK(vim->message() == std::string("VimNoTabMatch [NAME]=zeta")); go("b", "9"); - ensure_equals("no ninth", vim->message(), std::string("VimNoSuchTab [NUMBER]=9")); + CHECK(vim->message() == std::string("VimNoSuchTab [NUMBER]=9")); go("tabn", "9"); - ensure_equals("nor to go to", vim->message(), std::string("VimBadArgument")); - ensure_equals("still where it was", services.frontDoc()->id, std::string("delta")); + UNSCOPED_INFO("nor to go to"); + CHECK(vim->message() == std::string("VimBadArgument")); + UNSCOPED_INFO("still where it was"); + CHECK(services.frontDoc()->id == std::string("delta")); } - template<> template<> - void alscriptstudiovim_object::test<9>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: :bd and :tabclose and :tabonly close tabs and :tabmove moves one", "[alscriptstudiovim]") { - set_test_name(":bd and :tabclose close a tab, asked about or with ! let go of; :tabonly the others; :tabmove moves the one in front"); make(); ALVimKeymap* vim = fourTabs(); go("b", "beta"); - ensure(":bd the one in front, asked about", go("bd") == "beta" && studio.asked == Names{ "beta" }); - ensure(":bd! let go of", (go("bd!"), studio.letGo == Names{ "beta" })); - ensure(":bd by number", (go("bd", "3"), studio.asked == Names{ "beta", "gamma" })); - ensure(":tabclose! by number", (go("tabclose!", "4"), studio.letGo == Names{ "beta", "delta" })); - ensure(":tabonly asks about the others at once", (go("tabonly"), studio.many == Names{ "alpha", "gamma", "delta" })); + { + INFO(":bd the one in front, asked about"); + CHECK(go("bd") == "beta"); + CHECK(studio.asked == Names{ "beta" }); + } + UNSCOPED_INFO(":bd! let go of"); + CHECK((go("bd!"), studio.letGo == Names{ "beta" })); + UNSCOPED_INFO(":bd by number"); + CHECK((go("bd", "3"), studio.asked == Names{ "beta", "gamma" })); + UNSCOPED_INFO(":tabclose! by number"); + CHECK((go("tabclose!", "4"), studio.letGo == Names{ "beta", "delta" })); + UNSCOPED_INFO(":tabonly asks about the others at once"); + CHECK((go("tabonly"), studio.many == Names{ "alpha", "gamma", "delta" })); studio.letGo.clear(); - ensure(":tabonly! lets them go", (go("tabo!"), studio.letGo == Names{ "alpha", "gamma", "delta" })); - - ensure_equals(":tabm 0 to the start", (go("tabm", "0"), order()), std::string("beta alpha gamma delta")); - ensure_equals(":tabm to the end", (go("tabmove"), order()), std::string("alpha gamma delta beta")); - ensure_equals(":tabm 2 after the second", (go("tabm", "2"), order()), std::string("alpha gamma beta delta")); - ensure_equals(":tabm -1", (go("tabm", "-1"), order()), std::string("alpha beta gamma delta")); - ensure_equals(":tabm 3 after the third, past itself", (go("tabm", "3"), order()), std::string("alpha gamma beta delta")); - ensure_equals("still in front", services.frontDoc()->id, std::string("beta")); + UNSCOPED_INFO(":tabonly! lets them go"); + CHECK((go("tabo!"), studio.letGo == Names{ "alpha", "gamma", "delta" })); + + UNSCOPED_INFO(":tabm 0 to the start"); + CHECK((go("tabm", "0"), order()) == std::string("beta alpha gamma delta")); + UNSCOPED_INFO(":tabm to the end"); + CHECK((go("tabmove"), order()) == std::string("alpha gamma delta beta")); + UNSCOPED_INFO(":tabm 2 after the second"); + CHECK((go("tabm", "2"), order()) == std::string("alpha gamma beta delta")); + CHECK((go("tabm", "-1"), order()) == std::string("alpha beta gamma delta")); + UNSCOPED_INFO(":tabm 3 after the third, past itself"); + CHECK((go("tabm", "3"), order()) == std::string("alpha gamma beta delta")); + UNSCOPED_INFO("still in front"); + CHECK(services.frontDoc()->id == std::string("beta")); go("tabm", "+5"); - ensure_equals("not past the end", vim->message(), std::string("VimBadArgument")); - ensure_equals("where it was", order(), std::string("alpha gamma beta delta")); + UNSCOPED_INFO("not past the end"); + CHECK(vim->message() == std::string("VimBadArgument")); + UNSCOPED_INFO("where it was"); + CHECK(order() == std::string("alpha gamma beta delta")); } - template<> template<> - void alscriptstudiovim_object::test<10>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: :ls lists tabs with their flags and lines as vim lists buffers", "[alscriptstudiovim]") { - set_test_name(":ls lists the tabs as vim lists its buffers: the one in front, the one before, unsaved, unchangeable, and their lines"); make(); fourTabs(); go("b", "gamma"); @@ -585,21 +680,20 @@ namespace tut gamma.editor->setCaret(gamma.editor->document().end()); gamma.editor->insertText("\n\n"); services.findDoc(std::string_view("delta"))->modifiable = false; - ensure("taken", ex(*services.docs[0], "ls")); - ensure_equals("one entry", studio.entries.size(), size_t(1)); - ensure_equals("the tabs", studio.entries[0].text, - std::string("tabs:\n" - " 1 h \"alpha\" line 1\n" - " 2 %a \"beta\" line 1\n" - " 3 #h + \"gamma\" line 3\n" - " 4 h- \"delta\" line 1")); - ensure("the tab in sight", studio.outputShown == 1); + CHECK(ex(*services.docs[0], "ls")); + REQUIRE(studio.entries.size() == size_t(1)); + CHECK(studio.entries[0].text == + std::string("tabs:\n" + " 1 h \"alpha\" line 1\n" + " 2 %a \"beta\" line 1\n" + " 3 #h + \"gamma\" line 3\n" + " 4 h- \"delta\" line 1")); + UNSCOPED_INFO("the tab in sight"); + CHECK(studio.outputShown == 1); } - template<> template<> - void alscriptstudiovim_object::test<11>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: :e opens a file from the tab's folders or goes to a tab or reverts", "[alscriptstudiovim]") { - set_test_name(":e opens a file where the tab's folders have it, or a tab by its name or file, # the one before; alone, reads the tab again"); ALScriptStudioVim& vim = make(); files(); Doc& a = tab("alpha"); @@ -608,95 +702,137 @@ namespace tut tab("beta"); vim.pump(); - ensure("taken", ex(a, "e", "main.lsl")); - ensure("an LSL file as LSL", studio.openedFiles.size() == 1 && studio.openedFiles.back() == std::make_pair(in("main.lsl"), false)); + CHECK(ex(a, "e", "main.lsl")); + { + INFO("an LSL file as LSL"); + REQUIRE(studio.openedFiles.size() == 1); + CHECK(studio.openedFiles.back() == std::make_pair(in("main.lsl"), false)); + } ex(a, "edit", "lib/util.luau"); - ensure("in a folder under it, as SLua", - studio.openedFiles.size() == 2 && studio.openedFiles.back() == std::make_pair(in("lib/util.luau"), true)); + { + INFO("in a folder under it, as SLua"); + REQUIRE(studio.openedFiles.size() == 2); + CHECK(studio.openedFiles.back() == std::make_pair(in("lib/util.luau"), true)); + } ex(a, "e", "notes.txt"); - ensure("anything else as the tab it was named from is", - studio.openedFiles.size() == 3 && studio.openedFiles.back() == std::make_pair(in("notes.txt"), true)); + { + INFO("anything else as the tab it was named from is"); + REQUIRE(studio.openedFiles.size() == 3); + CHECK(studio.openedFiles.back() == std::make_pair(in("notes.txt"), true)); + } ex(a, "e", in("main.lsl")); - ensure("where a whole path says", studio.openedFiles.size() == 4 && studio.openedFiles.back().first == in("main.lsl")); + { + INFO("where a whole path says"); + REQUIRE(studio.openedFiles.size() == 4); + CHECK(studio.openedFiles.back().first == in("main.lsl")); + } ex(a, "tabe", "main.lsl"); - ensure(":tabedit the same", studio.openedFiles.size() == 5); + UNSCOPED_INFO(":tabedit the same"); + CHECK(studio.openedFiles.size() == 5); ex(a, "e", "nowhere.lsl"); - ensure_equals("nowhere: said", said->message(), std::string("VimNoFile [FILE]=nowhere.lsl")); - ensure("and nothing opened", studio.openedFiles.size() == 5); + UNSCOPED_INFO("nowhere: said"); + CHECK(said->message() == std::string("VimNoFile [FILE]=nowhere.lsl")); + UNSCOPED_INFO("and nothing opened"); + CHECK(studio.openedFiles.size() == 5); ex(a, "tabnew"); - ensure_equals(":tabnew wants a file", said->message(), std::string("VimArgumentRequired")); + UNSCOPED_INFO(":tabnew wants a file"); + CHECK(said->message() == std::string("VimArgumentRequired")); ex(a, "e", "beta"); - ensure_equals("a tab by its name", services.frontDoc()->id, std::string("beta")); + UNSCOPED_INFO("a tab by its name"); + CHECK(services.frontDoc()->id == std::string("beta")); vim.pump(); ex(a, "e", "#"); - ensure_equals("# the one before", services.frontDoc()->id, std::string("alpha")); + UNSCOPED_INFO("# the one before"); + CHECK(services.frontDoc()->id == std::string("alpha")); Doc& main = tab("mainfile"); main.file = in("main.lsl"); ex(a, "e", "main.lsl"); - ensure("a file open already is gone to", services.frontDoc() == &main && studio.openedFiles.size() == 5); + { + INFO("a file open already is gone to"); + CHECK(services.frontDoc() == &main); + CHECK(studio.openedFiles.size() == 5); + } ex(a, "e"); - ensure("alone: read again", studio.reverted == Names{ "alpha" }); + UNSCOPED_INFO("alone: read again"); + CHECK(studio.reverted == Names{ "alpha" }); a.editor->setCaret(a.editor->document().end()); a.editor->insertText(" "); ex(a, "e"); - ensure_equals("not over unsaved work", said->message(), std::string("VimNotSaved")); - ensure("left", studio.reverted.size() == 1); + UNSCOPED_INFO("not over unsaved work"); + CHECK(said->message() == std::string("VimNotSaved")); + UNSCOPED_INFO("left"); + CHECK(studio.reverted.size() == 1); ex(a, "e!"); - ensure("but with !", studio.reverted == Names{ "alpha", "alpha" }); + UNSCOPED_INFO("but with !"); + CHECK(studio.reverted == Names{ "alpha", "alpha" }); } - template<> template<> - void alscriptstudiovim_object::test<12>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: :r reads a file in and :w and :update and :wqa write", "[alscriptstudiovim]") { - set_test_name(":r puts a file below the caret's line; :w writes the text to one, over one there only with !; :update, :wqa"); make(); files(); Doc& a = tab("alpha", "one\ntwo"); ALVimKeymap* said = vimOver(a); ex(a, "r", "lib/util.luau"); - ensure_equals("below the line, its own last break dropped", a.editor->text(), std::string("one\nlocal x = 1\nreturn x\ntwo")); - ensure("the caret on the first line put in", a.editor->caret() == ALTextPos(1, 0)); + UNSCOPED_INFO("below the line, its own last break dropped"); + CHECK(a.editor->text() == std::string("one\nlocal x = 1\nreturn x\ntwo")); + UNSCOPED_INFO("the caret on the first line put in"); + CHECK(a.editor->caret() == ALTextPos(1, 0)); ex(a, "read", "nowhere"); - ensure_equals("nowhere: said", said->message(), std::string("VimNoFile [FILE]=nowhere")); + UNSCOPED_INFO("nowhere: said"); + CHECK(said->message() == std::string("VimNoFile [FILE]=nowhere")); ex(a, "r"); - ensure_equals("no file of its own", said->message(), std::string("VimNoFileName")); + UNSCOPED_INFO("no file of its own"); + CHECK(said->message() == std::string("VimNoFileName")); a.expandedEditor = editor("expanded_alpha", "x"); - ensure("not into the expansion", vim->command(*a.expandedEditor, "r", "main.lsl") && a.expandedEditor->text() == "x"); + { + INFO("not into the expansion"); + CHECK(vim->command(*a.expandedEditor, "r", "main.lsl")); + CHECK(a.expandedEditor->text() == "x"); + } ex(a, "w", "out.lsl"); - ensure_equals("written", contents(in("out.lsl")), a.editor->text()); + CHECK(contents(in("out.lsl")) == a.editor->text()); const std::string written = "VimWritten [BYTES]=" + std::to_string(a.editor->text().size()) + " [FILE]=" + in("out.lsl") + " [LINES]=4 [QUOTED]=\"" + in("out.lsl") + "\""; - ensure_equals("and said", said->message(), written); - ensure("not as an error", !said->messageIsError()); - ensure("the tab left as it was", ran.empty()); + CHECK(said->message() == written); + CHECK_FALSE(said->messageIsError()); + UNSCOPED_INFO("the tab left as it was"); + CHECK(ran.empty()); a.editor->setCaret(a.editor->document().end()); a.editor->insertText("!"); ex(a, "w", "out.lsl"); - ensure_equals("not over one there", said->message(), std::string("VimFileExists")); - ensure("left", contents(in("out.lsl")) != a.editor->text()); + UNSCOPED_INFO("not over one there"); + CHECK(said->message() == std::string("VimFileExists")); + UNSCOPED_INFO("left"); + CHECK(contents(in("out.lsl")) != a.editor->text()); ex(a, "w!", "out.lsl"); - ensure_equals("but with !", contents(in("out.lsl")), a.editor->text()); + UNSCOPED_INFO("but with !"); + CHECK(contents(in("out.lsl")) == a.editor->text()); ex(a, "w", "!ls"); - ensure_equals("no shell", said->message(), std::string("VimBadArgument")); + UNSCOPED_INFO("no shell"); + CHECK(said->message() == std::string("VimBadArgument")); ex(a, "update"); - ensure(":update over unsaved work saves", ran == Names{ "save" }); + UNSCOPED_INFO(":update over unsaved work saves"); + CHECK(ran == Names{ "save" }); a.editor->resetDirty(); ex(a, "up"); - ensure("over none, nothing", ran.size() == 1); + UNSCOPED_INFO("over none, nothing"); + CHECK(ran.size() == 1); tab("beta"); type(a, "?"); ex(a, "wqa"); - ensure(":wqa saves the unsaved to close, and closes the rest", - studio.savedToClose == Names{ "alpha" } && studio.asked == Names{ "beta" }); + { + INFO(":wqa saves the unsaved to close, and closes the rest"); + CHECK(studio.savedToClose == Names{ "alpha" }); + CHECK(studio.asked == Names{ "beta" }); + } } - template<> template<> - void alscriptstudiovim_object::test<13>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: Tab after a file command offers files and folders from the tab's folders", "[alscriptstudiovim]") { - set_test_name("Tab after :e, :r and :w offers the files and folders in the tabs' folders, or where a whole path says; hidden ones where asked"); ALScriptStudioVim& vim = make(); files(); tab("alpha"); @@ -706,64 +842,89 @@ namespace tut std::sort(out.begin(), out.end()); return out; }; - ensure("the folder's", offered("e", std::string()) == Names{ "lib/", "main.lsl", "notes.txt" }); - ensure("in a folder under it", offered("edit", "lib/u") == Names{ "lib/util.luau" }); - ensure("by the start of a name", offered("r", "ma") == Names{ "main.lsl" }); - ensure("after :w", offered("w", "n") == Names{ "notes.txt" }); - ensure("hidden where a dot is typed", offered("e", ".") == Names{ ".hidden" }); - ensure("where a whole path says", offered("tabe", folder + "/li") == Names{ folder + "/lib/" }); - ensure("not after anything else", offered("q", std::string()).empty()); + UNSCOPED_INFO("the folder's"); + CHECK(offered("e", std::string()) == Names{ "lib/", "main.lsl", "notes.txt" }); + UNSCOPED_INFO("in a folder under it"); + CHECK(offered("edit", "lib/u") == Names{ "lib/util.luau" }); + UNSCOPED_INFO("by the start of a name"); + CHECK(offered("r", "ma") == Names{ "main.lsl" }); + UNSCOPED_INFO("after :w"); + CHECK(offered("w", "n") == Names{ "notes.txt" }); + UNSCOPED_INFO("hidden where a dot is typed"); + CHECK(offered("e", ".") == Names{ ".hidden" }); + UNSCOPED_INFO("where a whole path says"); + CHECK(offered("tabe", folder + "/li") == Names{ folder + "/lib/" }); + UNSCOPED_INFO("not after anything else"); + CHECK(offered("q", std::string()).empty()); } - template<> template<> - void alscriptstudiovim_object::test<14>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: the quickfix and location commands step through the Problems tab", "[alscriptstudiovim]") { - set_test_name("the Problems tab is vim's quickfix and location lists: :cn and :cp step, :cc by number, :cfirst, :clast, :copen, :cclose"); make(); Doc& a = tab("a"); ALVimKeymap* said = vimOver(a); ex(a, "cn"); - ensure_equals("none: said", said->message(), std::string("VimNoErrors")); - ensure("nothing run", ran.empty()); + UNSCOPED_INFO("none: said"); + CHECK(said->message() == std::string("VimNoErrors")); + UNSCOPED_INFO("nothing run"); + CHECK(ran.empty()); problemsThere = true; - ensure(":cn, :cnext 2, :lne", ex(a, "cn") && ex(a, "cnext", "2") && ex(a, "lne")); - ensure("each a step", ran == Names(4, "next_problem")); + CHECK(ex(a, "cn")); + CHECK(ex(a, "cnext", "2")); + CHECK(ex(a, "lne")); + UNSCOPED_INFO("each a step"); + CHECK(ran == Names(4, "next_problem")); ran.clear(); - ensure(":cp, :cN, :lprevious 2", ex(a, "cp") && ex(a, "cN") && ex(a, "lprevious", "2")); - ensure("each a step back", ran == Names(4, "previous_problem")); + CHECK(ex(a, "cp")); + CHECK(ex(a, "cN")); + CHECK(ex(a, "lprevious", "2")); + UNSCOPED_INFO("each a step back"); + CHECK(ran == Names(4, "previous_problem")); ex(a, "cn", "x"); - ensure_equals("a count that is none", said->message(), std::string("VimBadArgument")); + UNSCOPED_INFO("a count that is none"); + CHECK(said->message() == std::string("VimBadArgument")); for (const auto& [name, args] : { std::pair{ "cc", "3" }, std::pair{ "cc", "" }, std::pair{ "ll", "2" }, std::pair{ "cfirst", "" }, std::pair{ "cr", "" }, std::pair{ "lfir", "" }, std::pair{ "clast", "" }, std::pair{ "lla", "" } }) { - ensure(name, ex(a, name, args)); + CAPTURE(name, args); + REQUIRE(ex(a, name, args)); } - ensure("by number: 0 at the caret, 1 the first, -1 the last", - studio.problemNumbers == std::vector{ 3, 0, 2, 1, 1, 1, -1, -1 }); + UNSCOPED_INFO("by number: 0 at the caret, 1 the first, -1 the last"); + CHECK(studio.problemNumbers == std::vector{ 3, 0, 2, 1, 1, 1, -1, -1 }); studio.hasProblems = false; ex(a, "cc", "1"); - ensure_equals("none to go to: said", said->message(), std::string("VimNoErrors")); + UNSCOPED_INFO("none to go to: said"); + CHECK(said->message() == std::string("VimNoErrors")); ran.clear(); ex(a, "copen"); - ensure(":copen shows the tab", ran == Names{ "problems" } && problemsShown); + { + INFO(":copen shows the tab"); + CHECK(ran == Names{ "problems" }); + CHECK(problemsShown); + } ex(a, "cw"); - ensure("shown already: left", ran.size() == 1); + UNSCOPED_INFO("shown already: left"); + CHECK(ran.size() == 1); ex(a, "ccl"); - ensure(":cclose folds it", ran.size() == 2 && !problemsShown); + { + INFO(":cclose folds it"); + CHECK(ran.size() == 2); + CHECK_FALSE(problemsShown); + } ex(a, "lcl"); - ensure("folded already: left", ran.size() == 2); + UNSCOPED_INFO("folded already: left"); + CHECK(ran.size() == 2); ex(a, "clist"); - ensure(":clist shows it", problemsShown); + UNSCOPED_INFO(":clist shows it"); + CHECK(problemsShown); } - template<> template<> - void alscriptstudiovim_object::test<15>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: vim's jumps and keys and :file and listings go through the window", "[alscriptstudiovim]") { - set_test_name("vim's jumps are the window's to go back to; Ctrl-O, Ctrl-I, K, :tag and :pop through the table; :file; lists to Output"); make(); Doc& a = tab("a", "one\ntwo\nthree\nfour"); ALVimKeymap* said = vimOver(a); @@ -773,78 +934,110 @@ namespace tut said->hooks().jumped(*a.expandedEditor, ALTextPos(1, 0)); navigation->unit.goBack(false); navigation->unit.goBack(false); - ensure("a jump kept where it began, in the view it was in", navigation->shown == Names{ "a expanded 1", "a 2" }); + UNSCOPED_INFO("a jump kept where it began, in the view it was in"); + CHECK(navigation->shown == Names{ "a expanded 1", "a 2" }); - ensure("Ctrl-O, Ctrl-I, K", said->hooks().command(*a.editor, "back", std::string()) && - said->hooks().command(*a.editor, "forward", std::string()) && - said->hooks().command(*a.editor, "reference", std::string())); - ensure(":tag, :pop", ex(a, "tag") && ex(a, "po")); - ensure("each its command", ran == Names{ "back", "forward", "reference", "go_to_definition", "back" }); + { + INFO("Ctrl-O, Ctrl-I, K"); + CHECK(said->hooks().command(*a.editor, "back", std::string())); + CHECK(said->hooks().command(*a.editor, "forward", std::string())); + CHECK(said->hooks().command(*a.editor, "reference", std::string())); + } + CHECK(ex(a, "tag")); + CHECK(ex(a, "po")); + UNSCOPED_INFO("each its command"); + CHECK(ran == Names{ "back", "forward", "reference", "go_to_definition", "back" }); ex(a, "tag", "llSay"); - ensure_equals("a name to go to is not taken", said->message(), std::string("VimBadArgument")); + UNSCOPED_INFO("a name to go to is not taken"); + CHECK(said->message() == std::string("VimBadArgument")); a.editor->setCaret(ALTextPos(1, 0)); ex(a, "file"); - ensure_equals(":file", said->message(), std::string("VimFileInfo [LINES]=4 [NAME]=a [PERCENT]=50 [QUOTED]=\"a\" [STATE]=")); - ensure("not as an error", !said->messageIsError()); + UNSCOPED_INFO(":file"); + CHECK(said->message() == std::string("VimFileInfo [LINES]=4 [NAME]=a [PERCENT]=50 [QUOTED]=\"a\" [STATE]=")); + CHECK_FALSE(said->messageIsError()); a.editor->insertText("!"); ex(a, "f"); - ensure_equals("unsaved", said->message(), std::string("VimFileInfo [LINES]=4 [NAME]=a [PERCENT]=50 [QUOTED]=\"a\" [STATE]=VimStateModified ")); + UNSCOPED_INFO("unsaved"); + CHECK(said->message() == std::string("VimFileInfo [LINES]=4 [NAME]=a [PERCENT]=50 [QUOTED]=\"a\" [STATE]=VimStateModified ")); said->hooks().listing(*a.editor, "mark line col file/text"); - ensure("a listing to Output", - studio.entries.size() == 1 && studio.entries[0].text == "mark line col file/text" && studio.outputShown == 1); + { + INFO("a listing to Output"); + REQUIRE(studio.entries.size() == 1); + CHECK(studio.entries[0].text == "mark line col file/text"); + CHECK(studio.outputShown == 1); + } } - template<> template<> - void alscriptstudiovim_object::test<16>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: :find opens an include or else a file as :e would or says it is nowhere", "[alscriptstudiovim]") { - set_test_name(":find opens an include or a module the script names, else a file as :e finds one, else says it is nowhere"); make(); files(); Doc& a = tab("a"); ALVimKeymap* said = vimOver(a); studio.included = { "lib/util.lsl" }; - ensure("taken", ex(a, "find", "lib/util.lsl")); - ensure("the include, by the name written", studio.includedAsked == Names{ "lib/util.lsl" } && studio.openedFiles.empty()); + CHECK(ex(a, "find", "lib/util.lsl")); + { + INFO("the include, by the name written"); + CHECK(studio.includedAsked == Names{ "lib/util.lsl" }); + CHECK(studio.openedFiles.empty()); + } ex(a, "fin", "main.lsl"); - ensure("not an include: a file where :e would find it", - studio.openedFiles.size() == 1 && studio.openedFiles.back().first == in("main.lsl")); + { + INFO("not an include: a file where :e would find it"); + REQUIRE(studio.openedFiles.size() == 1); + CHECK(studio.openedFiles.back().first == in("main.lsl")); + } ex(a, "tabfind", "nowhere.lsl"); - ensure_equals("nowhere", said->message(), std::string("VimNotInPath [NAME]=nowhere.lsl")); + CHECK(said->message() == std::string("VimNotInPath [NAME]=nowhere.lsl")); ex(a, "find"); - ensure_equals("a name wanted", said->message(), std::string("VimArgumentRequired")); + UNSCOPED_INFO("a name wanted"); + CHECK(said->message() == std::string("VimArgumentRequired")); } - template<> template<> - void alscriptstudiovim_object::test<17>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: :set spell and :set list and z= and gO go through the window", "[alscriptstudiovim]") { - set_test_name(":set spell and :set list are the window's; z= picks from a list over the editor; gO the symbols"); make(); Doc& a = tab("a"); ALVimKeymap* said = vimOver(a); - ensure(":set spell", ex(a, "set", "spell") && spellCheck && ran == Names{ "spell_check" }); - ensure("on already: left", ex(a, "set", "spell") && ran.size() == 1); - ensure(":set nospell", ex(a, "set", "nospell") && !spellCheck); - ensure(":set list, :set nolist", ex(a, "set", "list") && ex(a, "set", "nolist")); - ensure("the blanks all, then none", ran == Names{ "spell_check", "spell_check", "blanks_all", "blanks_none" }); + CHECK(ex(a, "set", "spell")); + CHECK(spellCheck); + CHECK(ran == Names{ "spell_check" }); + { + INFO("on already: left"); + CHECK(ex(a, "set", "spell")); + CHECK(ran.size() == 1); + } + CHECK(ex(a, "set", "nospell")); + CHECK_FALSE(spellCheck); + CHECK(ex(a, "set", "list")); + CHECK(ex(a, "set", "nolist")); + UNSCOPED_INFO("the blanks all, then none"); + CHECK(ran == Names{ "spell_check", "spell_check", "blanks_all", "blanks_none" }); std::vector chose; said->hooks().pick(*a.editor, "teh", { "the", "ten" }, [&chose](size_t index) { chose.push_back(index); }); - ensure("offered, in order", studio.offered == Names{ "0", "1" }); - ensure_equals("asking of the word", studio.asking, std::string("VimSuggestPlaceholder")); - ensure_equals("as tall as they are", studio.pickRows, 2); + UNSCOPED_INFO("offered, in order"); + CHECK(studio.offered == Names{ "0", "1" }); + UNSCOPED_INFO("asking of the word"); + CHECK(studio.asking == std::string("VimSuggestPlaceholder")); + UNSCOPED_INFO("as tall as they are"); + CHECK(studio.pickRows == 2); studio.pick("1"); - ensure("the one picked", chose == std::vector{ 1 }); + UNSCOPED_INFO("the one picked"); + CHECK(chose == std::vector{ 1 }); ran.clear(); - ensure("gO", said->hooks().command(*a.editor, "go_to_symbol", std::string()) && ran == Names{ "go_to_symbol" }); + { + INFO("gO"); + CHECK(said->hooks().command(*a.editor, "go_to_symbol", std::string())); + CHECK(ran == Names{ "go_to_symbol" }); + } } - template<> template<> - void alscriptstudiovim_object::test<18>() + TEST_CASE_METHOD(alscriptstudiovim_data, "alscriptstudiovim: the vimrc sets the window's toggles and each editor and reports the rest", "[alscriptstudiovim]") { - set_test_name("the vimrc read into the window's vim: the window's options through its toggles, each editor's set on it, what it did not take to Output"); make(); bool wrapped = false; commands.add( @@ -856,34 +1049,62 @@ namespace tut nullptr, [&wrapped]() { return wrapped; }); Doc& a = tab("a"); studio.vimrcText = "set nu wrap ts=2 et\nlet mapleader = ','\ninoremap jk \nbogus\n"; - ensure("not read yet", !vim->sourced()); + UNSCOPED_INFO("not read yet"); + CHECK_FALSE(vim->sourced()); vim->source(); - ensure("read", vim->sourced() && lineNumbers && wrapped); - ensure("the window's editors told", studio.editorsRefreshed == 1); + CHECK(vim->sourced()); + CHECK(lineNumbers); + CHECK(wrapped); + UNSCOPED_INFO("the window's editors told"); + CHECK(studio.editorsRefreshed == 1); const ALVimMappings& maps = vim->shared().mappings; - ensure("its mapping, and its leader", maps.match(ALVimMappings::INSERT, maps.keysOf("jk"), true).full && maps.leader() == ","); - ensure("what it did not take in the Output tab, not brought up", !studio.entries.empty() && - studio.entries.back().text.find("line 4:") != std::string::npos && - studio.entries.back().text.find("the vimrc") != std::string::npos && - studio.outputShown == 0); + { + INFO("its mapping, and its leader"); + CHECK(maps.match(ALVimMappings::INSERT, maps.keysOf("jk"), true).full); + CHECK(maps.leader() == ","); + } + { + INFO("what it did not take in the Output tab, not brought up"); + REQUIRE_FALSE(studio.entries.empty()); + CHECK(studio.entries.back().text.find("line 4:") != std::string::npos); + CHECK(studio.entries.back().text.find("the vimrc") != std::string::npos); + CHECK(studio.outputShown == 0); + } vim->applyViewOptions(*a.editor); - ensure("each editor's own set on it", a.editor->getTabWidth() == 2 && a.editor->getSoftTabs()); + { + INFO("each editor's own set on it"); + CHECK(a.editor->getTabWidth() == 2); + CHECK(a.editor->getSoftTabs()); + } // :set's ? shown, and the toggles. ALVimKeymap* said = vimOver(a); - ensure(":set nu?", ex(a, "set", "nu?") && said->message() == " number"); - ensure(":set invwrap", ex(a, "set", "invwrap") && !wrapped); + CHECK(ex(a, "set", "nu?")); + CHECK(said->message() == " number"); + CHECK(ex(a, "set", "invwrap")); + CHECK_FALSE(wrapped); // :source reads it again, or a tab as it; :e $MYVIMRC opens it. studio.vimrcText = "nnoremap Q gq\n"; - ensure(":so", ex(a, "so") && maps.match(ALVimMappings::NORMAL, maps.keysOf("Q"), true).full && - !maps.match(ALVimMappings::INSERT, maps.keysOf("jk"), true).full); + CHECK(ex(a, "so")); + CHECK(maps.match(ALVimMappings::NORMAL, maps.keysOf("Q"), true).full); + CHECK_FALSE(maps.match(ALVimMappings::INSERT, maps.keysOf("jk"), true).full); type(a, "\nbad line"); const bool read = ex(a, "source", "%"); const std::string& entry = studio.entries.back().text; - ensure(":so % reads the tab, and brings up what it did not take", - read && studio.outputShown == 1 && entry.find("line 2:") != std::string::npos && entry.find("the vimrc") == std::string::npos); - ensure(":e $MYVIMRC", ex(a, "e", "$MYVIMRC") && studio.vimrcEdited == 1); - ensure(":so of a file that is not there", ex(a, "so", "nowhere.vim") && said->messageIsError()); + { + INFO(":so % reads the tab, and brings up what it did not take"); + CHECK(read); + CHECK(studio.outputShown == 1); + CHECK(entry.find("line 2:") != std::string::npos); + CHECK(entry.find("the vimrc") == std::string::npos); + } + CHECK(ex(a, "e", "$MYVIMRC")); + CHECK(studio.vimrcEdited == 1); + { + INFO(":so of a file that is not there"); + CHECK(ex(a, "so", "nowhere.vim")); + CHECK(said->messageIsError()); + } } } diff --git a/indra/newview/tests/alscriptstudioweighing_test.cpp b/indra/newview/tests/alscriptstudioweighing_test.cpp index 1d5a4e7c8c..751d5ff9d3 100644 --- a/indra/newview/tests/alscriptstudioweighing_test.cpp +++ b/indra/newview/tests/alscriptstudioweighing_test.cpp @@ -30,7 +30,7 @@ #include "alscriptstudio_fixture.h" -#include "../test/lltut.h" +#include namespace { @@ -80,7 +80,7 @@ namespace } } -namespace tut +namespace { struct alscriptstudioweighing_data { @@ -93,7 +93,7 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } unit = std::make_unique(services, studio, studio, studio); return *unit; @@ -129,125 +129,181 @@ namespace tut } }; - typedef test_group alscriptstudioweighing_group; - typedef alscriptstudioweighing_group::object alscriptstudioweighing_object; - alscriptstudioweighing_group alscriptstudioweighing_instance("alscriptstudioweighing"); - - template<> template<> - void alscriptstudioweighing_object::test<1>() + // And the others while it is in front and the Weights tab looked at. + TEST_CASE_METHOD(alscriptstudioweighing_data, "alscriptstudioweighing: the target a tab is weighed for and none where it has no weigher", "[alscriptstudioweighing]") { - set_test_name("the target a tab is weighed for, none where it is not; and the others while it is in front and the Weights tab looked at"); ALScriptStudioWeighing& unit = make(); - ensure("Mono", unit.target(tab("mono", "x")) == Target::Mono); - ensure("no target said is Mono", unit.target(tab("none", "x", "")) == Target::Mono); - ensure("LSO", unit.target(tab("lso", "x", "lsl2")) == Target::LSO); - ensure("LSL on Luau", unit.target(tab("ll", "x", "lsl-luau")) == Target::LSLLuau); - ensure("SLua", unit.target(tab("slua", "x", "luau")) == Target::SLua); - ensure("a target with no weigher", !unit.target(tab("odd", "x", "something"))); + CHECK(unit.target(tab("mono", "x")) == Target::Mono); + UNSCOPED_INFO("no target said is Mono"); + CHECK(unit.target(tab("none", "x", "")) == Target::Mono); + CHECK(unit.target(tab("lso", "x", "lsl2")) == Target::LSO); + CHECK(unit.target(tab("ll", "x", "lsl-luau")) == Target::LSLLuau); + CHECK(unit.target(tab("slua", "x", "luau")) == Target::SLua); + UNSCOPED_INFO("a target with no weigher"); + CHECK_FALSE(unit.target(tab("odd", "x", "something"))); Doc& note = tab("note", "x"); note.notecard = true; - ensure("a notecard", !unit.target(note)); + UNSCOPED_INFO("a notecard"); + CHECK_FALSE(unit.target(note)); Doc& loading = tab("loading", "x"); loading.loaded = false; - ensure("not loaded", !unit.target(loading)); + UNSCOPED_INFO("not loaded"); + CHECK_FALSE(unit.target(loading)); studio.fragment = true; - ensure("a fragment", !unit.target(tab("fragment", "x"))); + UNSCOPED_INFO("a fragment"); + CHECK_FALSE(unit.target(tab("fragment", "x"))); studio.fragment = false; services.front = 0; - ensure("its own alone", unit.targets(*services.docs[0]).size() == 1); + UNSCOPED_INFO("its own alone"); + CHECK(unit.targets(*services.docs[0]).size() == 1); studio.shown = true; const std::vector all = unit.targets(*services.docs[0]); - ensure("in front, the tab looked at: the other two as well", all.size() == 3 && all[0] == Target::Mono); - ensure("not in front: its own", unit.targets(*services.docs[2]).size() == 1); + { + INFO("in front, the tab looked at: the other two as well"); + REQUIRE(all.size() == 3); + CHECK(all[0] == Target::Mono); + } + UNSCOPED_INFO("not in front: its own"); + CHECK(unit.targets(*services.docs[2]).size() == 1); services.front = 4; - ensure("SLua has no others", unit.targets(*services.docs[4]).size() == 1); + UNSCOPED_INFO("SLua has no others"); + CHECK(unit.targets(*services.docs[4]).size() == 1); } - template<> template<> - void alscriptstudioweighing_object::test<2>() + // Exact where nothing after the check changes it. + TEST_CASE_METHOD(alscriptstudioweighing_data, "alscriptstudioweighing: a weight with its check is kept and told and an older answer dropped", "[alscriptstudioweighing]") { - set_test_name("weighed with its check: kept, told, and exact where nothing after the check changes it; an answer for older text dropped"); ALScriptStudioWeighing& unit = make(); Doc& a = tab("a", "default {}"); unit.weigh(a); - ensure("asked", a.weighing->asking && studio.asked == Names{ "a" }); + CHECK(a.weighing->asking); + CHECK(studio.asked == Names{ "a" }); ALScriptAnalysis::Result old = answerFor(a, { weightOf(Target::Mono, 100) }); old.version -= 1; unit.weighed(a, old); - ensure("older: dropped", !a.weighing->asking && !a.weighing->weight && a.weighing->all.empty()); + { + INFO("older: dropped"); + CHECK_FALSE(a.weighing->asking); + CHECK_FALSE(a.weighing->weight); + CHECK(a.weighing->all.empty()); + } unit.weighed(a, answerFor(a, { weightOf(Target::Mono, 100), weightOf(Target::LSO, 200) })); - ensure("kept, its own first", a.weighing->weight && a.weighing->weight->total == 100 && a.weighing->all.size() == 2 && - a.weighing->allVersion == versionOf(a) && a.weighing->version == versionOf(a)); - ensure("exact: no optimizer after the check", a.weighing->exact && !a.weighing->sent); - ensure("the Problems and the save told", studio.problems == Names{ "a" } && studio.warned == Names{ "a" }); + { + INFO("kept, its own first"); + REQUIRE(a.weighing->weight); + CHECK(a.weighing->weight->total == 100); + CHECK(a.weighing->all.size() == 2); + CHECK(a.weighing->allVersion == versionOf(a)); + CHECK(a.weighing->version == versionOf(a)); + } + { + INFO("exact: no optimizer after the check"); + CHECK(a.weighing->exact); + CHECK_FALSE(a.weighing->sent); + } + { + INFO("the Problems and the save told"); + CHECK(studio.problems == Names{ "a" }); + CHECK(studio.warned == Names{ "a" }); + } studio.preprocessing = true; studio.optimizer = true; unit.weighed(a, answerFor(a, { weightOf(Target::Mono, 90) })); - ensure("the optimizer after the check: not exact", !a.weighing->exact && a.weighing->weight->total == 90); + { + INFO("the optimizer after the check: not exact"); + CHECK_FALSE(a.weighing->exact); + CHECK(a.weighing->weight->total == 90); + } Doc& none = tab("none", "x"); none.notecard = true; none.weighing->weight = weightOf(Target::Mono, 1); unit.weigh(none); - ensure("nothing to weigh it for: its weight let go, nothing asked", !none.weighing->weight && studio.asked.size() == 1); + { + INFO("nothing to weigh it for: its weight let go, nothing asked"); + CHECK_FALSE(none.weighing->weight); + CHECK(studio.asked.size() == 1); + } } - template<> template<> - void alscriptstudioweighing_object::test<3>() + // An answer for older text is dropped. + TEST_CASE_METHOD(alscriptstudioweighing_data, "alscriptstudioweighing: a sent text's weight stands over the check's of the same text", "[alscriptstudioweighing]") { - set_test_name("what a save sends weighed as sent, which stands over the check's of the same text; an answer for older text dropped"); ALScriptStudioWeighing& unit = make(); Doc& a = tab("a", "default {}"); unit.weighSent(a); - ensure("nothing sent yet: nothing asked", studio.requests.empty()); + UNSCOPED_INFO("nothing sent yet: nothing asked"); + CHECK(studio.requests.empty()); a.uploaded.valid = true; a.uploaded.version = versionOf(a); a.uploaded.text = std::make_shared("sent text"); unit.weighSent(a); - ensure("asked of what was sent, for its own target", studio.requests.size() == 1 && *studio.requests[0].text == "sent text" && - studio.requests[0].targets == std::vector{ Target::Mono }); + { + INFO("asked of what was sent, for its own target"); + REQUIRE(studio.requests.size() == 1); + CHECK(*studio.requests[0].text == "sent text"); + CHECK(studio.requests[0].targets == std::vector{ Target::Mono }); + } studio.answers[0](answerFor(a, { weightOf(Target::Mono, 300) })); - ensure("kept as sent, and exact", a.weighing->weight && a.weighing->weight->total == 300 && a.weighing->sent && a.weighing->exact); - ensure("told", studio.problems == Names{ "a" } && studio.warned == Names{ "a" }); + { + INFO("kept as sent, and exact"); + REQUIRE(a.weighing->weight); + CHECK(a.weighing->weight->total == 300); + CHECK(a.weighing->sent); + CHECK(a.weighing->exact); + } + { + INFO("the Problems and the save told"); + CHECK(studio.problems == Names{ "a" }); + CHECK(studio.warned == Names{ "a" }); + } unit.weighed(a, answerFor(a, { weightOf(Target::Mono, 100) })); - ensure("the check's of the same text does not replace it", a.weighing->weight->total == 300 && a.weighing->all.size() == 1); + { + INFO("the check's of the same text does not replace it"); + CHECK(a.weighing->weight->total == 300); + CHECK(a.weighing->all.size() == 1); + } a.uploaded.disabled = true; unit.weighSent(a); - ensure("the preprocessor off: the text as written", *studio.requests[1].text == "default {}"); + UNSCOPED_INFO("the preprocessor off: the text as written"); + CHECK(*studio.requests[1].text == "default {}"); a.editor->setCaret(a.editor->document().end()); a.editor->insertText(" "); ALScriptAnalysis::Result late = answerFor(a, { weightOf(Target::Mono, 1) }); late.version -= 1; studio.answers[1](late); - ensure("typed on since: dropped", a.weighing->weight->total == 300); + UNSCOPED_INFO("typed on since: dropped"); + CHECK(a.weighing->weight->total == 300); } - template<> template<> - void alscriptstudioweighing_object::test<4>() + TEST_CASE_METHOD(alscriptstudioweighing_data, "alscriptstudioweighing: the saved text's weight is kept target by target for the change column", "[alscriptstudioweighing]") { - set_test_name("what the text weighs while it is the text saved is kept for the change column, target by target"); ALScriptStudioWeighing& unit = make(); Doc& a = tab("a", "default {}"); a.weighing->all = { weightOf(Target::Mono, 100), weightOf(Target::LSO, 200) }; a.weighing->allVersion = versionOf(a); unit.keepSaved(a); - ensure("kept", a.weighing->saved.size() == 2); + CHECK(a.weighing->saved.size() == 2); a.weighing->all = { weightOf(Target::Mono, 150) }; unit.keepSaved(a); - ensure("each target's replaced, the others kept", a.weighing->saved.size() == 2 && a.weighing->saved[0].total == 150 && - a.weighing->saved[1].total == 200); + { + INFO("each target's replaced, the others kept"); + REQUIRE(a.weighing->saved.size() == 2); + CHECK(a.weighing->saved[0].total == 150); + CHECK(a.weighing->saved[1].total == 200); + } a.editor->setCaret(a.editor->document().end()); a.editor->insertText(" "); a.weighing->allVersion = versionOf(a); a.weighing->all = { weightOf(Target::Mono, 1) }; unit.keepSaved(a); - ensure("not saved: nothing kept", a.weighing->saved[0].total == 150); + UNSCOPED_INFO("not saved: nothing kept"); + CHECK(a.weighing->saved[0].total == 150); } - template<> template<> - void alscriptstudioweighing_object::test<5>() + // Neither where it is off or out of date. + TEST_CASE_METHOD(alscriptstudioweighing_data, "alscriptstudioweighing: a weight shows each part's bytes and each line's heat in the editor", "[alscriptstudioweighing]") { - set_test_name("a weight shown in the editor: each part's bytes after its line, each line's heat; neither where it is off or out of date"); ALScriptStudioWeighing& unit = make(); Doc& a = tab("a", "integer f() { return 1; }\ndefault {}\n"); ALScriptWeight weight = weightOf(Target::Mono, 100); @@ -265,42 +321,57 @@ namespace tut a.weighing->version = versionOf(a); a.weighing->exact = true; unit.showInEditor(a); - ensure("its part's bytes after its line", a.editor->noteAt(0).find("WeightNote") != std::string::npos); - ensure("the included file's own not", a.editor->noteAt(1).empty()); - ensure("the warmest line the heat's own colour", a.editor->heatAt(0) == 1.f); - ensure_equals("a quarter as warm by bytes, half by their square root", a.editor->heatAt(1), 0.5f); + UNSCOPED_INFO("its part's bytes after its line"); + CHECK(a.editor->noteAt(0).find("WeightNote") != std::string::npos); + UNSCOPED_INFO("the included file's own not"); + CHECK(a.editor->noteAt(1).empty()); + UNSCOPED_INFO("the warmest line the heat's own colour"); + CHECK(a.editor->heatAt(0) == 1.f); + UNSCOPED_INFO("a quarter as warm by bytes, half by their square root"); + CHECK(a.editor->heatAt(1) == 0.5f); studio.notes = false; unit.showInEditor(a); - ensure("notes off: gone, the heat kept", a.editor->noteAt(0).empty() && a.editor->heatAt(0) == 1.f); + { + INFO("notes off: gone, the heat kept"); + CHECK(a.editor->noteAt(0).empty()); + CHECK(a.editor->heatAt(0) == 1.f); + } studio.heat = false; unit.showInEditor(a); - ensure("heat off: gone", a.editor->heatAt(0) == 0.f); + UNSCOPED_INFO("heat off: gone"); + CHECK(a.editor->heatAt(0) == 0.f); studio.notes = true; studio.heat = true; a.weighing->version -= 1; unit.showInEditor(a); - ensure("out of date: nothing new said", a.editor->noteAt(0).empty() && a.editor->heatAt(0) == 0.f); + { + INFO("out of date: nothing new said"); + CHECK(a.editor->noteAt(0).empty()); + CHECK(a.editor->heatAt(0) == 0.f); + } } - template<> template<> - void alscriptstudioweighing_object::test<6>() + TEST_CASE_METHOD(alscriptstudioweighing_data, "alscriptstudioweighing: a save's size is measured once a text and not for a file on disk", "[alscriptstudioweighing]") { - set_test_name("what a save would send, measured once for each text: a script's, a notecard's, nothing for a file on disk"); ALScriptStudioWeighing& unit = make(); Doc& a = tab("a", "default {}"); unit.measureAsset(a); - ensure_equals("the script's text", a.weighing->assetBytes, size_t(10)); + UNSCOPED_INFO("the script's text"); + CHECK(a.weighing->assetBytes == size_t(10)); a.weighing->assetBytes = 0; unit.measureAsset(a); - ensure("measured once for a text", a.weighing->assetBytes == 0); + UNSCOPED_INFO("measured once for a text"); + CHECK(a.weighing->assetBytes == 0); Doc& n = tab("n", "note text"); n.notecard = true; unit.measureAsset(n); - ensure_equals("a notecard's", n.weighing->assetBytes, size_t(9)); + UNSCOPED_INFO("a notecard's"); + CHECK(n.weighing->assetBytes == size_t(9)); Doc& f = tab("f", "file text"); f.file = "/somewhere/f.lsl"; unit.measureAsset(f); - ensure_equals("a file on disk has no limit", f.weighing->assetBytes, size_t(0)); + UNSCOPED_INFO("a file on disk has no limit"); + CHECK(f.weighing->assetBytes == size_t(0)); // Preprocessed: the envelope a save would send, measured as long as // it would be made. Doc& p = tab("p", "#define X 1\ndefault { /* x */ }"); @@ -310,13 +381,12 @@ namespace tut p.expanded.generation = 1; p.expanded.text = std::make_shared("default { }\n"); unit.measureAsset(p); - ensure_equals("the envelope's length", p.weighing->assetBytes, p.envelopeFor(*p.expanded.text, studio.programVersion()).wrap().size()); + CHECK(p.weighing->assetBytes == p.envelopeFor(*p.expanded.text, studio.programVersion()).wrap().size()); } - template<> template<> - void alscriptstudioweighing_object::test<7>() + // Each against the text as it stands. + TEST_CASE_METHOD(alscriptstudioweighing_data, "alscriptstudioweighing: listed fixes are weighed once settled and told what each would change", "[alscriptstudioweighing]") { - set_test_name("the fixes listed weighed once the list settles, each against the text as it stands; told what each would change"); ALScriptStudioWeighing& unit = make(); Doc& a = tab("a", "integer a = 1\n"); ALCodeEditor& e = *a.editor; @@ -340,27 +410,42 @@ namespace tut e.setFixesShown([&unit, &a](U32 shown, const std::vector& fixes) { unit.fixesShown(a.id, shown, fixes); }); e.setFixable(0, true, true); e.setFocus(true); - ensure("listed", e.handleKeyHere('.', MASK_CONTROL) && e.fixesOpen()); + { + INFO("Ctrl+. lists the fixes"); + CHECK(e.handleKeyHere('.', MASK_CONTROL)); + CHECK(e.fixesOpen()); + } unit.pump(); - ensure_equals("weighed: the text, and each fix's copy but the suppression's", studio.requests.size(), size_t(1)); + UNSCOPED_INFO("weighed: the text, and each fix's copy but the suppression's"); + REQUIRE(studio.requests.size() == size_t(1)); const ALScriptAnalysis::Request& request = studio.requests[0]; - ensure("the text first, then the copies", request.variants.size() == 3 && request.variants[0] == "integer a = 1\n" && - request.variants[1] == "integer a = 1;\n" && request.variants[2] == "integer b = 1\n"); + { + INFO("the text first, then the copies"); + REQUIRE(request.variants.size() == 3); + CHECK(request.variants[0] == "integer a = 1\n"); + CHECK(request.variants[1] == "integer a = 1;\n"); + CHECK(request.variants[2] == "integer b = 1\n"); + } ALScriptAnalysis::Result result; result.id = a.id; result.version = versionOf(a); result.variantTotals = { 100, 90, 100 }; studio.answers[0](result); - ensure("the lighter one said", e.fixes()[0].note.find("FixLighterEstimate") == 0); - ensure("the same: nothing said", e.fixes()[1].note.empty() && e.fixes()[2].note.empty()); + UNSCOPED_INFO("the lighter one said"); + CHECK(e.fixes()[0].note.find("FixLighterEstimate") == 0); + { + INFO("the same: nothing said"); + CHECK(e.fixes()[1].note.empty()); + CHECK(e.fixes()[2].note.empty()); + } unit.pump(); - ensure("weighed once", studio.requests.size() == 1); + UNSCOPED_INFO("weighed once"); + CHECK(studio.requests.size() == 1); } - template<> template<> - void alscriptstudioweighing_object::test<8>() + // While the Weights tab is looked at, and the tab checked as it stands. + TEST_CASE_METHOD(alscriptstudioweighing_data, "alscriptstudioweighing: the tab in front is weighed once for the targets it lacks", "[alscriptstudioweighing]") { - set_test_name("while the Weights tab is looked at, the tab in front checked as it stands is weighed for the targets it lacks, once"); ALScriptStudioWeighing& unit = make(); Doc& a = tab("a", "default {}"); services.front = 0; @@ -370,23 +455,30 @@ namespace tut a.weighing->allVersion = versionOf(a); a.weighing->askedFor = versionOf(a); unit.pump(); - ensure("the others wanted: weighed", studio.asked == Names{ "a" } && a.weighing->asking); + { + INFO("the others wanted: weighed"); + CHECK(studio.asked == Names{ "a" }); + CHECK(a.weighing->asking); + } unit.pump(); - ensure("once, while it is asked", studio.asked.size() == 1); + UNSCOPED_INFO("once, while it is asked"); + CHECK(studio.asked.size() == 1); a.weighing->asking = false; a.weighing->all = { weightOf(Target::Mono, 1), weightOf(Target::LSO, 2), weightOf(Target::LSLLuau, 3) }; unit.pump(); - ensure("all there: nothing more", studio.asked.size() == 1); + UNSCOPED_INFO("all there: nothing more"); + CHECK(studio.asked.size() == 1); studio.shown = false; a.weighing->all.clear(); unit.pump(); - ensure("not looked at: nothing", studio.asked.size() == 1); + UNSCOPED_INFO("not looked at: nothing"); + CHECK(studio.asked.size() == 1); } - template<> template<> - void alscriptstudioweighing_object::test<9>() + // Checked but not weighed with it; one asked for an older text is not + // waited on. + TEST_CASE_METHOD(alscriptstudioweighing_data, "alscriptstudioweighing: a tab come to the front unweighed is weighed once for its text", "[alscriptstudioweighing]") { - set_test_name("a tab come to the front, checked but not weighed with it, is weighed once for its text; one asked for an older text is not waited on"); ALScriptStudioWeighing& unit = make(); Doc& a = tab("a", "default {}"); Doc& b = tab("b", "default {}"); @@ -395,20 +487,29 @@ namespace tut a.weighing->askedFor = versionOf(a); services.front = 0; unit.pump(); - ensure("weighed with its check already: nothing", studio.asked.empty()); + UNSCOPED_INFO("weighed with its check already: nothing"); + CHECK(studio.asked.empty()); services.front = 1; unit.pump(); - ensure("come to the front: weighed", studio.asked == Names{ "b" } && ALScriptStudioWeighing::asking(b)); + { + INFO("come to the front: weighed"); + CHECK(studio.asked == Names{ "b" }); + CHECK(ALScriptStudioWeighing::asking(b)); + } unit.pump(); - ensure("once", studio.asked.size() == 1); + UNSCOPED_INFO("weighed once"); + CHECK(studio.asked.size() == 1); // Typed in while it was weighed: the answer for the older text is // passed over, and the check of the new one weighs it again. b.editor->insertText("x"); - ensure("not waited on", !ALScriptStudioWeighing::asking(b)); + UNSCOPED_INFO("not waited on"); + CHECK_FALSE(ALScriptStudioWeighing::asking(b)); unit.pump(); - ensure("not before its check", studio.asked.size() == 1); + UNSCOPED_INFO("not before its check"); + CHECK(studio.asked.size() == 1); b.check->analysisVersion = versionOf(b); unit.pump(); - ensure("then, for the new text", studio.asked == (Names{ "b", "b" })); + UNSCOPED_INFO("then, for the new text"); + CHECK(studio.asked == (Names{ "b", "b" })); } } diff --git a/indra/newview/tests/alscriptstudiowords_test.cpp b/indra/newview/tests/alscriptstudiowords_test.cpp index d7d9d913e9..378661423b 100644 --- a/indra/newview/tests/alscriptstudiowords_test.cpp +++ b/indra/newview/tests/alscriptstudiowords_test.cpp @@ -28,7 +28,7 @@ #include "alscriptstudio_fixture.h" -#include "../test/lltut.h" +#include #include @@ -72,7 +72,7 @@ namespace } } -namespace tut +namespace { struct alscriptstudiowords_data { @@ -99,7 +99,7 @@ namespace tut { if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } ALCodeEditor::Params p(LLUICtrlFactory::getDefaultParams()); p.name = "editor"; @@ -121,92 +121,132 @@ namespace tut } }; - typedef test_group alscriptstudiowords_group; - typedef alscriptstudiowords_group::object alscriptstudiowords_object; - alscriptstudiowords_group alscriptstudiowords_instance("alscriptstudiowords"); - - template<> template<> - void alscriptstudiowords_object::test<1>() + TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: the vocabulary gives each kind's detail sorted and skips unknown groups", "[alscriptstudiowords]") { - set_test_name("the vocabulary from the definitions: each kind's detail, the deprecated marked, sorted, a group it does not know left out"); const std::vector& words = Words::vocabulary(false); - ensure_equals("six", words.size(), size_t(6)); - ensure("sorted", std::is_sorted(words.begin(), words.end(), [](const Vocab& a, const Vocab& b) { return a.text < b.text; })); + CHECK(words.size() == size_t(6)); + CHECK(std::is_sorted(words.begin(), words.end(), [](const Vocab& a, const Vocab& b) { return a.text < b.text; })); const Vocab* say = Words::word(false, "llSay"); - ensure("a function's call, with its arguments' types", say && say->kind == ALSyntaxKind::Function && - say->detail == "llSay(integer channel, string msg)"); - ensure("and what returns", Words::word(false, "llOld")->detail == "integer llOld()" && Words::word(false, "llOld")->deprecated); - ensure_equals("an event's", Words::word(false, "touch_start")->detail, std::string("touch_start(integer num_detected)")); - ensure_equals("a constant's type and value", Words::word(false, "PI")->detail, std::string("float PI = 3.14159265")); - ensure_equals("a type's tooltip's first line", Words::word(false, "integer")->detail, std::string("A whole number.")); - ensure("a control", Words::word(false, "jump")->kind == ALSyntaxKind::Control); - ensure("not a word", !Words::word(false, "x") && !Words::word(false, "") && !Words::word(false, "nothing")); - ensure_equals("SLua's () returns nothing worth saying", Words::word(true, "ll.Say")->detail, std::string("ll.Say(number channel)")); + { + INFO("a function's call, with its arguments' types"); + REQUIRE(say); + CHECK(say->kind == ALSyntaxKind::Function); + CHECK(say->detail == "llSay(integer channel, string msg)"); + } + { + INFO("and what returns"); + CHECK(Words::word(false, "llOld")->detail == "integer llOld()"); + CHECK(Words::word(false, "llOld")->deprecated); + } + UNSCOPED_INFO("an event's"); + CHECK(Words::word(false, "touch_start")->detail == std::string("touch_start(integer num_detected)")); + UNSCOPED_INFO("a constant's type and value"); + CHECK(Words::word(false, "PI")->detail == std::string("float PI = 3.14159265")); + UNSCOPED_INFO("a type's tooltip's first line"); + CHECK(Words::word(false, "integer")->detail == std::string("A whole number.")); + CHECK(Words::word(false, "jump")->kind == ALSyntaxKind::Control); + { + INFO("not a word"); + CHECK_FALSE(Words::word(false, "x")); + CHECK_FALSE(Words::word(false, "")); + CHECK_FALSE(Words::word(false, "nothing")); + } + UNSCOPED_INFO("SLua's () returns nothing worth saying"); + CHECK(Words::word(true, "ll.Say")->detail == std::string("ll.Say(number channel)")); } - template<> template<> - void alscriptstudiowords_object::test<2>() + TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: the vocabulary is kept until forgotten and then built again", "[alscriptstudiowords]") { - set_test_name("the vocabulary kept until forgotten, then built again from the definitions as they are, the lookup with it"); - ensure("there", Words::word(false, "llSay") != nullptr); + REQUIRE(Words::word(false, "llSay") != nullptr); lsl["functions"].erase("llSay"); lsl["functions"]["llShout"] = LLSD().with("return", ""); - ensure("kept", Words::word(false, "llSay") != nullptr && !Words::word(false, "llShout")); + { + INFO("kept"); + CHECK(Words::word(false, "llSay") != nullptr); + CHECK_FALSE(Words::word(false, "llShout")); + } Words::forget(); - ensure("built again", !Words::word(false, "llSay") && Words::word(false, "llShout") != nullptr); + { + INFO("built again"); + CHECK_FALSE(Words::word(false, "llSay")); + CHECK(Words::word(false, "llShout") != nullptr); + } viewer.slots.keywords = [](bool) { return LLSD("not a map"); }; Words::forget(); - ensure("definitions that are not: no words", Words::vocabulary(false).empty() && !Words::word(false, "llShout")); + { + INFO("definitions that are not: no words"); + CHECK(Words::vocabulary(false).empty()); + CHECK_FALSE(Words::word(false, "llShout")); + } } - template<> template<> - void alscriptstudiowords_object::test<3>() + TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: an editor is taught each kind's table and the preprocessor's words", "[alscriptstudiowords]") { - set_test_name("an editor taught the words: each kind's table, the deprecated apart, and LSL's preprocessor words while they are on"); ALCodeEditor& e = editor("default {}", false); extra = { "switch", "case" }; Words::teach(e, false); const ALSyntaxWords& tables = e.highlighter().words(); - ensure("functions", tables.has("function", "llSay") && !tables.has("function", "llOld")); - ensure("the deprecated", tables.has("deprecated", "llOld")); - ensure("events, constants, types, controls", tables.has("event", "touch_start") && tables.has("constant", "PI") && - tables.has("type", "integer") && tables.has("control", "jump")); - ensure("the preprocessor's", tables.has("control", "switch") && tables.has("control", "case")); + CHECK(tables.has("function", "llSay")); + CHECK_FALSE(tables.has("function", "llOld")); + CHECK(tables.has("deprecated", "llOld")); + CHECK(tables.has("event", "touch_start")); + CHECK(tables.has("constant", "PI")); + CHECK(tables.has("type", "integer")); + CHECK(tables.has("control", "jump")); + { + INFO("the preprocessor's"); + CHECK(tables.has("control", "switch")); + CHECK(tables.has("control", "case")); + } ALCodeEditor& s = editor("x", true); Words::teach(s, true); - ensure("SLua's own, and none of the preprocessor's", s.highlighter().words().has("function", "ll.Say") && - !s.highlighter().words().has("control", "switch")); + { + INFO("SLua's own, and none of the preprocessor's"); + CHECK(s.highlighter().words().has("function", "ll.Say")); + CHECK_FALSE(s.highlighter().words().has("control", "switch")); + } } - template<> template<> - void alscriptstudiowords_object::test<4>() + TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: inStateBody finds a state's body past braces in strings and comments", "[alscriptstudiowords]") { - set_test_name("straight inside a state or not, braces in strings and comments passed over"); ALCodeEditor& e = editor("default\n{\n \n touch_start(integer n)\n {\n \n }\n}\n" "state /* named */ two { \"{\" // {\n \n}\n", false); - ensure("in default", Words::inStateBody(e, ALTextPos(2, 4))); - ensure("in a handler: not", !Words::inStateBody(e, ALTextPos(5, 8))); - ensure("after default: not", !Words::inStateBody(e, ALTextPos(8, 0))); - ensure("in a state named, a comment between, past a brace in a string and a comment", Words::inStateBody(e, ALTextPos(9, 2))); + UNSCOPED_INFO("in default"); + CHECK(Words::inStateBody(e, ALTextPos(2, 4))); + UNSCOPED_INFO("in a handler: not"); + CHECK_FALSE(Words::inStateBody(e, ALTextPos(5, 8))); + UNSCOPED_INFO("after default: not"); + CHECK_FALSE(Words::inStateBody(e, ALTextPos(8, 0))); + UNSCOPED_INFO("in a state named, a comment between, past a brace in a string and a comment"); + CHECK(Words::inStateBody(e, ALTextPos(9, 2))); } - template<> template<> - void alscriptstudiowords_object::test<5>() + TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: completions fill in handlers where they go and offer snippets", "[alscriptstudiowords]") { - set_test_name("completions: a word, an event as a handler to fill in in either language; a handler only where one goes; snippets"); const ALCodeEditor::Completion lsl = Words::completionFor(*Words::word(false, "touch_start"), false); - ensure_equals("LSL's handler", lsl.snippet, std::string("touch_start(integer num_detected)\n{\n $0\n}")); + UNSCOPED_INFO("LSL's handler"); + CHECK(lsl.snippet == std::string("touch_start(integer num_detected)\n{\n $0\n}")); const ALCodeEditor::Completion slua = Words::completionFor(*Words::word(true, "touch_start"), true); - ensure_equals("SLua's, a function of LLEvents'", slua.snippet, std::string("function LLEvents.touch_start(detected)\n $0\nend")); - ensure_equals("typed where asked, as the definitions type it", Words::completionFor(*Words::word(true, "touch_start"), true, true).snippet, - std::string("function LLEvents.touch_start(detected: {DetectedEvent})\n $0\nend")); - ensure_equals("each parameter typed", Words::completionFor(*Words::word(true, "listen"), true, true).snippet, - std::string("function LLEvents.listen(channel: number, name: string, id: uuid, msg: string)\n $0\nend")); - ensure_equals("each untyped", Words::completionFor(*Words::word(true, "listen"), true).snippet, - std::string("function LLEvents.listen(channel, name, id, msg)\n $0\nend")); + UNSCOPED_INFO("SLua's, a function of LLEvents'"); + CHECK(slua.snippet == std::string("function LLEvents.touch_start(detected)\n $0\nend")); + UNSCOPED_INFO("typed where asked, as the definitions type it"); + CHECK(Words::completionFor(*Words::word(true, "touch_start"), true, true).snippet == + std::string("function LLEvents.touch_start(detected: {DetectedEvent})\n $0\nend")); + UNSCOPED_INFO("each parameter typed"); + CHECK(Words::completionFor(*Words::word(true, "listen"), true, true).snippet == + std::string("function LLEvents.listen(channel: number, name: string, id: uuid, msg: string)\n $0\nend")); + UNSCOPED_INFO("each untyped"); + CHECK(Words::completionFor(*Words::word(true, "listen"), true).snippet == + std::string("function LLEvents.listen(channel, name, id, msg)\n $0\nend")); const ALCodeEditor::Completion say = Words::completionFor(*Words::word(false, "llSay"), false); - ensure("a function as itself", say.snippet.empty() && say.text == "llSay" && say.documentation && *say.documentation == "Says msg on channel.\nMore."); + { + INFO("a function as itself"); + CHECK(say.snippet.empty()); + CHECK(say.text == "llSay"); + REQUIRE(say.documentation); + CHECK(*say.documentation == "Says msg on channel.\nMore."); + } ALCodeEditor& e = editor("default\n{\n t\n}\nt\n", false); ALScriptSnippets::Snippet snippet; @@ -215,137 +255,191 @@ namespace tut snippet.body = "llSetTimerEvent($1);"; std::vector out; Words::complete(false, e, ALTextPos(2, 5), "t", { snippet }, "Snippet", out); - ensure("inside the state: the handler and the snippet", texts(out) == std::vector{ "touch_start", "timer" }); - ensure_equals("the snippet said so", out[1].detail, std::string("Snippet Timer")); + UNSCOPED_INFO("inside the state: the handler and the snippet"); + REQUIRE(texts(out) == std::vector{ "touch_start", "timer" }); + UNSCOPED_INFO("the snippet said so"); + CHECK(out[1].detail == std::string("Snippet Timer")); out.clear(); Words::complete(false, e, ALTextPos(4, 1), "t", { snippet }, "Snippet", out); - ensure("outside: no handler", texts(out) == std::vector{ "timer" }); + UNSCOPED_INFO("outside: no handler"); + CHECK(texts(out) == std::vector{ "timer" }); out.clear(); ALScriptSnippets::Snippet member = snippet; member.prefix = "ll.Snip"; Words::complete(true, e, ALTextPos(4, 1), "ll.S", { member }, "Snippet", out); - ensure("a member: the vocabulary's alone", texts(out) == std::vector{ "ll.Say" }); + UNSCOPED_INFO("a member: the vocabulary's alone"); + CHECK(texts(out) == std::vector{ "ll.Say" }); } - template<> template<> - void alscriptstudiowords_object::test<6>() + TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: hover text names a member in full and notes the deprecated", "[alscriptstudiowords]") { - set_test_name("the hover card's words: a member asked as the whole name, a word as itself, the deprecated said; a word of the script's none"); ALCodeEditor& s = editor("ll.Say(0)\nx = math.pi\n", true); std::string text; - ensure("ll.Say under Say", Words::hoverText(true, s.document(), ALTextPos(0, 4), "Say", text) && text == "ll.Say(number channel)"); - ensure("math.pi under pi", Words::hoverText(true, s.document(), ALTextPos(1, 10), "pi", text) && text == "number math.pi()"); - ensure("a word of the script's", !Words::hoverText(true, s.document(), ALTextPos(1, 0), "x", text)); + { + INFO("ll.Say under Say"); + CHECK(Words::hoverText(true, s.document(), ALTextPos(0, 4), "Say", text)); + CHECK(text == "ll.Say(number channel)"); + } + { + INFO("math.pi under pi"); + CHECK(Words::hoverText(true, s.document(), ALTextPos(1, 10), "pi", text)); + CHECK(text == "number math.pi()"); + } + UNSCOPED_INFO("a word of the script's"); + CHECK_FALSE(Words::hoverText(true, s.document(), ALTextPos(1, 0), "x", text)); ALCodeEditor& e = editor("llOld()\n", false); - ensure("the deprecated", Words::hoverText(false, e.document(), ALTextPos(0, 2), "llOld", text) && - text == "integer llOld()\nGone.\n" + ALCodeEditor::deprecatedNote()); + { + INFO("the deprecated"); + CHECK(Words::hoverText(false, e.document(), ALTextPos(0, 2), "llOld", text)); + CHECK(text == "integer llOld()\nGone.\n" + ALCodeEditor::deprecatedNote()); + } } - template<> template<> - void alscriptstudiowords_object::test<7>() + TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: the Insert menu's lists and each word's reference page", "[alscriptstudiowords]") { - set_test_name("the Insert menu's list and the reference: a word's page on the wiki, SLua's ll names too, the libraries', the portal"); ALScriptSnippets::Snippet snippet; snippet.name = "Timer"; snippet.prefix = "timer"; snippet.detail = "Every so often"; const std::vector snippets = Words::library(false, "snippet", { snippet }, "Deprecated"); - ensure("a snippet, by what it is", snippets.size() == 1 && snippets[0].value == "Timer\ntimer" && snippets[0].also == "timer"); + { + INFO("a snippet, by what it is"); + REQUIRE(snippets.size() == 1); + CHECK(snippets[0].value == "Timer\ntimer"); + CHECK(snippets[0].also == "timer"); + } const std::vector functions = Words::library(false, "function", {}, "Deprecated"); - ensure("the functions alone", functions.size() == 2 && functions[0].label == "llOld" && functions[1].label == "llSay"); - ensure("the deprecated said so, the rest by their tooltip's first line", - functions[0].detail == "Deprecated" && functions[1].detail == "Says msg on channel."); - ensure("the events", Words::library(false, "event", {}, "") .size() == 1); - - ensure_equals("an LSL name", Words::helpUrl(false, "llSay"), std::string("https://wiki.example/llSay")); - ensure_equals("SLua's ll name, as LSL's", Words::helpUrl(true, "ll.Say"), std::string("https://wiki.example/llSay")); - ensure_equals("a library's", Words::helpUrl(true, "math.pi"), std::string("https://luau.org/library")); - ensure_equals("the rest", Words::helpUrl(true, "LLEvents"), std::string("https://wiki.secondlife.com/wiki/Lua_Alpha")); - ensure_equals("nothing: the portal", Words::helpUrl(false, ""), std::string("https://wiki.example/LSL_Portal")); - ensure_equals("the reference's words", Words::referenceText(*Words::word(false, "PI"), false), - std::string("float PI = 3.14159265\n\nThe ratio.\nhttps://wiki.example/PI")); + { + INFO("the functions alone"); + REQUIRE(functions.size() == 2); + CHECK(functions[0].label == "llOld"); + CHECK(functions[1].label == "llSay"); + } + { + INFO("the deprecated said so, the rest by their tooltip's first line"); + CHECK(functions[0].detail == "Deprecated"); + CHECK(functions[1].detail == "Says msg on channel."); + } + CHECK(Words::library(false, "event", {}, "") .size() == 1); + + UNSCOPED_INFO("an LSL name"); + CHECK(Words::helpUrl(false, "llSay") == std::string("https://wiki.example/llSay")); + UNSCOPED_INFO("SLua's ll name, as LSL's"); + CHECK(Words::helpUrl(true, "ll.Say") == std::string("https://wiki.example/llSay")); + UNSCOPED_INFO("a library's"); + CHECK(Words::helpUrl(true, "math.pi") == std::string("https://luau.org/library")); + UNSCOPED_INFO("the rest"); + CHECK(Words::helpUrl(true, "LLEvents") == std::string("https://wiki.secondlife.com/wiki/Lua_Alpha")); + UNSCOPED_INFO("nothing: the portal"); + CHECK(Words::helpUrl(false, "") == std::string("https://wiki.example/LSL_Portal")); + UNSCOPED_INFO("the reference's words"); + CHECK(Words::referenceText(*Words::word(false, "PI"), false) == + std::string("float PI = 3.14159265\n\nThe ratio.\nhttps://wiki.example/PI")); } - template<> template<> - void alscriptstudiowords_object::test<8>() + TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: a tab's mark follows what it is and a symbol's its kind", "[alscriptstudiowords]") { - set_test_name("the marks: a tab's by what it is, a symbol's by its kind"); ALScriptStudioDoc doc; - ensure_equals("an LSL script", std::string(Words::imageNameOf(doc)), std::string("Inv_Script")); + UNSCOPED_INFO("an LSL script"); + CHECK(std::string(Words::imageNameOf(doc)) == std::string("Inv_Script")); doc.language.lua = true; - ensure_equals("an SLua one", std::string(Words::imageNameOf(doc)), std::string("Inv_Script_Luau")); + UNSCOPED_INFO("an SLua one"); + CHECK(std::string(Words::imageNameOf(doc)) == std::string("Inv_Script_Luau")); doc.notecard = true; - ensure_equals("a notecard", std::string(Words::imageNameOf(doc)), std::string("Inv_Notecard")); + UNSCOPED_INFO("a notecard"); + CHECK(std::string(Words::imageNameOf(doc)) == std::string("Inv_Notecard")); doc.file = "/a/notes.txt"; - ensure_equals("a file", std::string(Words::imageNameOf(doc)), std::string("Studio_File")); + UNSCOPED_INFO("a file"); + CHECK(std::string(Words::imageNameOf(doc)) == std::string("Studio_File")); doc.name = ".luaurc"; - ensure_equals("a config", std::string(Words::imageNameOf(doc)), std::string("Studio_Config")); - ensure_equals("a function", std::string(Words::imageNameOf(ALScriptSymbolKind::Function)), std::string("Symbol_Function")); - ensure_equals("a state as a label", std::string(Words::imageNameOf(ALScriptSymbolKind::State)), std::string("Symbol_Label")); + UNSCOPED_INFO("a config"); + CHECK(std::string(Words::imageNameOf(doc)) == std::string("Studio_Config")); + CHECK(std::string(Words::imageNameOf(ALScriptSymbolKind::Function)) == std::string("Symbol_Function")); + UNSCOPED_INFO("a state as a label"); + CHECK(std::string(Words::imageNameOf(ALScriptSymbolKind::State)) == std::string("Symbol_Label")); } - template<> template<> - void alscriptstudiowords_object::test<9>() + TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: each symbol kind has a window word of its own", "[alscriptstudiowords]") { - set_test_name("each kind of symbol called by one of the window's words, a word of its own"); if (!window.floater) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } std::set said; for (S32 kind = static_cast(ALScriptSymbolKind::Keyword); kind <= static_cast(ALScriptSymbolKind::Module); ++kind) { const char* word = Words::kindWordOf(static_cast(kind)); - ensure("a word for each", word != nullptr && window.floater->hasString(word)); + CAPTURE(kind); + REQUIRE(word != nullptr); + REQUIRE(window.floater->hasString(word)); said.insert(window.floater->getString(word)); } - ensure_equals("each its own", said.size(), size_t(static_cast(ALScriptSymbolKind::Module) + 1)); - ensure_equals("a function", window.floater->getString(Words::kindWordOf(ALScriptSymbolKind::Function)), std::string("function")); + UNSCOPED_INFO("each its own"); + CHECK(said.size() == size_t(static_cast(ALScriptSymbolKind::Module) + 1)); + CHECK(window.floater->getString(Words::kindWordOf(ALScriptSymbolKind::Function)) == std::string("function")); } - template<> template<> - void alscriptstudiowords_object::test<10>() + TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: a builtin's notes say its delay and god mode and not for a member", "[alscriptstudiowords]") { - set_test_name("what the definitions say of a builtin beyond its declaration: its words, its forced delay, that only a god may call it; a member is no global of its name"); lsl["functions"]["llEmail"] = LLSD().with("return", "").with("sleep", "20.0").with("tooltip", "Sends an email."); lsl["functions"]["llGodLikeRezObject"] = LLSD().with("return", "").with("god-mode", true).with("sleep", "0.0"); slua["functions"]["type"] = LLSD().with("return", "string").with("tooltip", "What a value is."); Words::forget(); const Vocab* email = Words::word(false, "llEmail"); - ensure("the email", email && email->sleep == "20.0" && !email->godMode); + REQUIRE(email); + CHECK(email->sleep == "20.0"); + CHECK_FALSE(email->godMode); const std::string notes = Words::notesOf(*email); - ensure("its words and its delay: " + notes, notes.find("Sends an email.") == 0 && notes.find("20.0") != std::string::npos); + { + INFO("its words and its delay: " << notes); + CHECK(notes.find("Sends an email.") == 0); + CHECK(notes.find("20.0") != std::string::npos); + } const Vocab* god = Words::word(false, "llGodLikeRezObject"); - ensure("a god's, no delay said for none", god && god->godMode && god->sleep.empty()); - ensure("said", !Words::notesOf(*god).empty()); + { + INFO("a god's, no delay said for none"); + REQUIRE(god); + CHECK(god->godMode); + CHECK(god->sleep.empty()); + } + UNSCOPED_INFO("that only a god may call it, said"); + CHECK_FALSE(Words::notesOf(*god).empty()); // `type` under the mouse in `obj.type` is some table's field. ALCodeEditor& s = editor("local t = obj.type\nlocal u = type(t)\n", true); std::string text; - ensure("a member: not the global", !Words::hoverText(true, s.document(), ALTextPos(0, 15), "type", text)); - ensure("the global itself", Words::hoverText(true, s.document(), ALTextPos(1, 11), "type", text) && text.find("What a value is.") != std::string::npos); + UNSCOPED_INFO("a member: not the global"); + CHECK_FALSE(Words::hoverText(true, s.document(), ALTextPos(0, 15), "type", text)); + { + INFO("the global itself"); + CHECK(Words::hoverText(true, s.document(), ALTextPos(1, 11), "type", text)); + CHECK(text.find("What a value is.") != std::string::npos); + } } - template<> template<> - void alscriptstudiowords_object::test<11>() + TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: linkArgument finds the link-number argument as the definitions name it", "[alscriptstudiowords]") { - set_test_name("a function's link-number argument, as the definitions name it, in LSL and in SLua"); lsl["functions"]["llSetLinkAlpha"] = LLSD().with("return", "").with( "arguments", LLSD().with(0, argument("LinkNumber", "integer")).with(1, argument("Opacity", "float")).with(2, argument("Face", "integer"))); slua["functions"]["ll.MessageLinked"] = LLSD().with("return", "()").with("arguments", LLSD().with(0, argument("link", "number")).with(1, argument("num", "number"))); Words::forget(); - ensure("llSetLinkAlpha's first", Words::linkArgument(false, "llSetLinkAlpha", 0)); - ensure("not its second", !Words::linkArgument(false, "llSetLinkAlpha", 1)); - ensure("nor past its last", !Words::linkArgument(false, "llSetLinkAlpha", 3)); - ensure("llSay has none", !Words::linkArgument(false, "llSay", 0)); - ensure("a word not known, none", !Words::linkArgument(false, "llNothing", 0)); - ensure("SLua's by its name with its library", Words::linkArgument(true, "ll.MessageLinked", 0) && !Words::linkArgument(true, "ll.MessageLinked", 1)); + CHECK(Words::linkArgument(false, "llSetLinkAlpha", 0)); + CHECK_FALSE(Words::linkArgument(false, "llSetLinkAlpha", 1)); + UNSCOPED_INFO("nor past its last"); + CHECK_FALSE(Words::linkArgument(false, "llSetLinkAlpha", 3)); + CHECK_FALSE(Words::linkArgument(false, "llSay", 0)); + UNSCOPED_INFO("a word not known, none"); + CHECK_FALSE(Words::linkArgument(false, "llNothing", 0)); + { + INFO("SLua's by its name with its library"); + CHECK(Words::linkArgument(true, "ll.MessageLinked", 0)); + CHECK_FALSE(Words::linkArgument(true, "ll.MessageLinked", 1)); + } } - template<> template<> - void alscriptstudiowords_object::test<12>() + + TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: word tables are shared per language and rebuilt when their sources change", "[alscriptstudiowords]") { - set_test_name("a language's tables are made once and shared by every editor of it; new definitions are read once however many ask; new preprocessor words make new tables"); S32 asked = 0; std::string version = "one"; viewer.slots.keywords = [this, &asked](bool lua) { @@ -360,30 +454,39 @@ namespace tut Words::teach(a, false); Words::teach(b, false); Words::teach(s, true); - ensure("one set for both LSL editors", &a.highlighter().words() == &b.highlighter().words()); - ensure("another for SLua's", &s.highlighter().words() != &a.highlighter().words()); + UNSCOPED_INFO("one set for both LSL editors"); + CHECK(&a.highlighter().words() == &b.highlighter().words()); + UNSCOPED_INFO("another for SLua's"); + CHECK(&s.highlighter().words() != &a.highlighter().words()); const S32 first = asked; Words::teach(a, false); - ensure_equals("taught again from what is kept", asked, first); + UNSCOPED_INFO("taught again from what is kept"); + CHECK(asked == first); version = "two"; lsl["functions"]["llShout"] = LLSD().with("return", ""); Words::teach(a, false); Words::teach(b, false); - ensure_equals("new definitions read once for both", asked, first + 1); - ensure("and shared still, with the new word", &a.highlighter().words() == &b.highlighter().words() && - a.highlighter().words().has("function", "llShout")); + UNSCOPED_INFO("new definitions read once for both"); + CHECK(asked == first + 1); + { + INFO("and shared still, with the new word"); + CHECK(&a.highlighter().words() == &b.highlighter().words()); + CHECK(a.highlighter().words().has("function", "llShout")); + } const ALSyntaxWords* before = &a.highlighter().words(); extra = { "switch" }; Words::teach(a, false); - ensure("new preprocessor words, new tables", &a.highlighter().words() != before && a.highlighter().words().has("control", "switch")); + { + INFO("new preprocessor words, new tables"); + CHECK(&a.highlighter().words() != before); + CHECK(a.highlighter().words().has("control", "switch")); + } } - template<> template<> - void alscriptstudiowords_object::test<13>() + TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: the reference groups each word by category or library or family", "[alscriptstudiowords]") { - set_test_name("the reference files each word: a function by its first category, an SLua ll function by its LSL twin's, other SLua by library, a constant by family"); Words::Categories found; Words::readCategories("constants:\n" " PI:\n" @@ -406,34 +509,46 @@ namespace tut " categories:\n" " - touch\n", found); - ensure_equals("only functions, and those with categories", found.size(), size_t(1)); - ensure_equals("by the first", found["llSay"], std::string("avatar_communication")); + UNSCOPED_INFO("only functions, and those with categories"); + CHECK(found.size() == size_t(1)); + UNSCOPED_INFO("by the first"); + CHECK(found["llSay"] == std::string("avatar_communication")); viewer.slots.definitionsYaml = [] { return std::string("functions:\n llSay:\n categories:\n - avatar_communication\n"); }; lsl["constants"]["PRIM_POSITION"] = LLSD().with("type", "integer").with("value", "6"); Words::forget(); - ensure_equals("LSL, in words", Words::word(false, "llSay")->group, std::string("Avatar communication")); - ensure("none where none is said", Words::word(false, "llOld")->group.empty()); - ensure("an event none", Words::word(false, "touch_start")->group.empty()); - ensure_equals("a constant by family", Words::word(false, "PRIM_POSITION")->group, std::string("PRIM_...")); - ensure("one without a family none", Words::word(false, "PI")->group.empty()); - ensure_equals("SLua's ll function as its twin", Words::word(true, "ll.Say")->group, std::string("Avatar communication")); - ensure_equals("another SLua function by library", Words::word(true, "math.pi")->group, std::string("math")); + UNSCOPED_INFO("LSL, in words"); + CHECK(Words::word(false, "llSay")->group == std::string("Avatar communication")); + UNSCOPED_INFO("none where none is said"); + CHECK(Words::word(false, "llOld")->group.empty()); + UNSCOPED_INFO("an event none"); + CHECK(Words::word(false, "touch_start")->group.empty()); + UNSCOPED_INFO("a constant by family"); + CHECK(Words::word(false, "PRIM_POSITION")->group == std::string("PRIM_...")); + UNSCOPED_INFO("one without a family none"); + CHECK(Words::word(false, "PI")->group.empty()); + UNSCOPED_INFO("SLua's ll function as its twin"); + CHECK(Words::word(true, "ll.Say")->group == std::string("Avatar communication")); + UNSCOPED_INFO("another SLua function by library"); + CHECK(Words::word(true, "math.pi")->group == std::string("math")); } - template<> template<> - void alscriptstudiowords_object::test<14>() + TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: the units ask the attached viewer and default what it leaves unsaid", "[alscriptstudiowords]") { - set_test_name("the units ask the viewer attached; what it leaves unsaid is a viewer with nothing's answer"); ALScriptStudioViewer& asked = ALScriptStudioViewer::get(); - ensure("the test's is the one asked", &asked == &viewer); - ensure("its words", asked.lslHelpUrl() == "https://wiki.example/[LSL_STRING]"); - ensure("no definitions as YAML, no version", asked.definitionsYaml().empty() && asked.definitionsVersion().empty()); - ensure("no preprocessor, no transform", !asked.preprocessing() && !asked.transformOn(ALPreprocessor::Transform::Switch)); + UNSCOPED_INFO("the test's is the one asked"); + CHECK(&asked == &viewer); + UNSCOPED_INFO("its words"); + CHECK(asked.lslHelpUrl() == "https://wiki.example/[LSL_STRING]"); + CHECK(asked.definitionsYaml().empty()); + CHECK(asked.definitionsVersion().empty()); + CHECK_FALSE(asked.preprocessing()); + CHECK_FALSE(asked.transformOn(ALPreprocessor::Transform::Switch)); ALPreprocessor::Include found; - ensure("no include", asked.lookUp(ALScriptPreprocessor::Request(), ALPreprocessor::Ask(), found) == ALPreprocessor::Found::No); + CHECK(asked.lookUp(ALScriptPreprocessor::Request(), ALPreprocessor::Ask(), found) == ALPreprocessor::Found::No); viewer.slots.lslHelpUrl = nullptr; - ensure_equals("the wiki as nothing says it", asked.lslHelpUrl(), std::string("[LSL_STRING]")); + UNSCOPED_INFO("the wiki as nothing says it"); + CHECK(asked.lslHelpUrl() == std::string("[LSL_STRING]")); } // The call a string is an argument of, and what kind of item of the @@ -441,8 +556,7 @@ namespace tut // the string and the bracket or comma before it; SLua's call of a string // alone; nothing for a string that is not an argument's start, nor one // of an argument that names no item. - template<> template<> - void alscriptstudiowords_object::test<15>() + TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: a string argument says its call and the kind of item it names", "[alscriptstudiowords]") { using Item = ALLSLTraits::Item; const auto kind = [this](const std::string& text, bool lua, char* quote = nullptr) { @@ -458,29 +572,48 @@ namespace tut return found; }; char quote = '\0'; - ensure("on its line", kind("llPlaySound(\"do", false, "e) == Item::Sound && quote == '"'); - ensure("a second argument", kind("llGiveInventory(llGetOwner(), \"x", false) == Item::Any); - ensure("lines and comments between", - kind("llGiveInventory(llGetOwner(),\n // what to give\n /* by name */\n \"x", false) == Item::Any); - ensure("many lines between", kind("llRezObject(\n\n\n\n\n\n\"obj", false) == Item::Object); - ensure("SLua's, by its own name", kind("ll.StartAnimation('wa", true, "e) == Item::Animation && quote == '\''); - ensure("SLua's with no brackets", kind("ll.PlaySound \"do", true) == Item::Sound); - ensure("and none in LSL, which has no such call", kind("llPlaySound \"do", false) == Item::None); - ensure("not an argument naming an item", kind("llSay(0, \"x", false) == Item::None); - ensure("not a string that only ends an argument", kind("llPlaySound(prefix + \"x", false) == Item::None); - ensure("not after a keyword", kind("return \"x", true) == Item::None); - ensure("not in a comment", kind("// llPlaySound(\"x", false) == Item::None); + { + INFO("on its line"); + CHECK(kind("llPlaySound(\"do", false, "e) == Item::Sound); + CHECK(quote == '"'); + } + UNSCOPED_INFO("a second argument"); + CHECK(kind("llGiveInventory(llGetOwner(), \"x", false) == Item::Any); + UNSCOPED_INFO("lines and comments between"); + CHECK(kind("llGiveInventory(llGetOwner(),\n // what to give\n /* by name */\n \"x", false) == Item::Any); + UNSCOPED_INFO("many lines between"); + CHECK(kind("llRezObject(\n\n\n\n\n\n\"obj", false) == Item::Object); + { + INFO("SLua's, by its own name"); + CHECK(kind("ll.StartAnimation('wa", true, "e) == Item::Animation); + CHECK(quote == '\''); + } + UNSCOPED_INFO("SLua's with no brackets"); + CHECK(kind("ll.PlaySound \"do", true) == Item::Sound); + UNSCOPED_INFO("and none in LSL, which has no such call"); + CHECK(kind("llPlaySound \"do", false) == Item::None); + UNSCOPED_INFO("not an argument naming an item"); + CHECK(kind("llSay(0, \"x", false) == Item::None); + UNSCOPED_INFO("not a string that only ends an argument"); + CHECK(kind("llPlaySound(prefix + \"x", false) == Item::None); + UNSCOPED_INFO("not after a keyword"); + CHECK(kind("return \"x", true) == Item::None); + UNSCOPED_INFO("not in a comment"); + CHECK(kind("// llPlaySound(\"x", false) == Item::None); std::string callee; S32 argument = -1; ALCodeEditor& e = editor("llSetLinkAlpha(2, ", false); - ensure("the call around a place", Words::callAt(e, ALTextPos(0, 18), callee, argument) && callee == "llSetLinkAlpha" && argument == 1); + { + INFO("the call around a place"); + CHECK(Words::callAt(e, ALTextPos(0, 18), callee, argument)); + CHECK(callee == "llSetLinkAlpha"); + CHECK(argument == 1); + } } - template<> template<> - void alscriptstudiowords_object::test<16>() + TEST_CASE_METHOD(alscriptstudiowords_data, "alscriptstudiowords: SLua handler parameters keep their types' own commas and variadics", "[alscriptstudiowords]") { - set_test_name("an SLua handler's parameters split as a signature's are: a type's own commas, colons and arrow its own, a variadic `...` however written"); slua["events"]["mapped"] = LLSD().with("arguments", LLSD().with(0, argument("pairs", "Map")).with(1, argument("n", "number"))); slua["events"]["called"] = LLSD().with("arguments", LLSD().with(0, argument("back", "(number) -> ()")).with(1, argument("data", "{[string]: any}"))); @@ -488,13 +621,20 @@ namespace tut slua["events"]["bare"] = LLSD().with("arguments", LLSD().with(0, argument("...any", ""))); Words::forget(); const auto handler = [](const char* name, bool typed) { return Words::completionFor(*Words::word(true, name), true, typed).snippet; }; - ensure_equals("a generic's comma", handler("mapped", true), std::string("function LLEvents.mapped(pairs: Map, n: number)\n $0\nend")); - ensure_equals("and untyped", handler("mapped", false), std::string("function LLEvents.mapped(pairs, n)\n $0\nend")); - ensure_equals("a function type's arrow, a table type's colon", handler("called", true), - std::string("function LLEvents.called(back: (number) -> (), data: {[string]: any})\n $0\nend")); - ensure_equals("a variadic typed", handler("varied", true), std::string("function LLEvents.varied(first: number, ...: any)\n $0\nend")); - ensure_equals("and not", handler("varied", false), std::string("function LLEvents.varied(first, ...)\n $0\nend")); - ensure_equals("one written ...any", handler("bare", true), std::string("function LLEvents.bare(...: any)\n $0\nend")); - ensure_equals("and untyped", handler("bare", false), std::string("function LLEvents.bare(...)\n $0\nend")); + UNSCOPED_INFO("a generic's comma"); + CHECK(handler("mapped", true) == std::string("function LLEvents.mapped(pairs: Map, n: number)\n $0\nend")); + UNSCOPED_INFO("and untyped"); + CHECK(handler("mapped", false) == std::string("function LLEvents.mapped(pairs, n)\n $0\nend")); + UNSCOPED_INFO("a function type's arrow, a table type's colon"); + CHECK(handler("called", true) == + std::string("function LLEvents.called(back: (number) -> (), data: {[string]: any})\n $0\nend")); + UNSCOPED_INFO("a variadic typed"); + CHECK(handler("varied", true) == std::string("function LLEvents.varied(first: number, ...: any)\n $0\nend")); + UNSCOPED_INFO("and not"); + CHECK(handler("varied", false) == std::string("function LLEvents.varied(first, ...)\n $0\nend")); + UNSCOPED_INFO("one written ...any"); + CHECK(handler("bare", true) == std::string("function LLEvents.bare(...: any)\n $0\nend")); + UNSCOPED_INFO("and untyped"); + CHECK(handler("bare", false) == std::string("function LLEvents.bare(...)\n $0\nend")); } } diff --git a/indra/newview/tests/alscripttheme_test.cpp b/indra/newview/tests/alscripttheme_test.cpp index b7092e39bc..298010d193 100644 --- a/indra/newview/tests/alscripttheme_test.cpp +++ b/indra/newview/tests/alscripttheme_test.cpp @@ -35,13 +35,13 @@ #include "../../llui/tests/alheadlessui_fixture.h" -#include "../test/lltut.h" +#include #include #include #include -namespace tut +namespace { struct alscripttheme_data { @@ -54,17 +54,11 @@ namespace tut } }; - typedef test_group alscripttheme_test; - typedef alscripttheme_test::object alscripttheme_object; - tut::alscripttheme_test alscripttheme_testgroup("alscripttheme"); - - template<> template<> - void alscripttheme_object::test<1>() + TEST_CASE_METHOD(alscripttheme_data, "alscripttheme: every shipped theme loads and colours only names the editor knows", "[alscripttheme]") { - set_test_name("every theme the viewer ships reads, and colours only what a theme speaks for"); if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } S32 shipped = 0; for (const ALScriptTheme& theme : ALScriptTheme::available()) @@ -74,40 +68,43 @@ namespace tut continue; } ++shipped; - ensure("a name: " + theme.path, !theme.name.empty()); - ensure("colours: " + theme.name, !theme.colors.empty()); + CAPTURE(theme.path); + REQUIRE_FALSE(theme.name.empty()); + REQUIRE_FALSE(theme.colors.empty()); for (const auto& [name, color] : theme.colors) { - ensure(theme.name + " colours " + name + ", which no editor is painted by", spoken(name)); + UNSCOPED_INFO("it colours " << name << ", which no editor is painted by"); + REQUIRE(spoken(name)); } } - ensure("the viewer ships themes", shipped > 1); + CHECK(shipped > 1); } - template<> template<> - void alscripttheme_object::test<2>() + TEST_CASE_METHOD(alscripttheme_data, "alscripttheme: editor colours come first and every colour name has a label", "[alscripttheme]") { - set_test_name("the editor's own colours come first, and every name reads as words"); const std::vector& names = ALScriptTheme::names(); - ensure("names", !names.empty()); - ensure_equals("the text first", names.front(), std::string("ScriptText")); - ensure("an editor colour", ALScriptTheme::isEditorColor("ScriptBackground")); - ensure_equals("read as a person reads it", ALScriptTheme::labelOf("ScriptBackground"), std::string("Background")); - ensure("a kind is not the editor's", !ALScriptTheme::isEditorColor(names.back())); - ensure_equals("a kind's name spaced", ALScriptTheme::labelOf("ScriptDocComment"), std::string("Doc comment")); + REQUIRE_FALSE(names.empty()); + UNSCOPED_INFO("the text first"); + CHECK(names.front() == std::string("ScriptText")); + CHECK(ALScriptTheme::isEditorColor("ScriptBackground")); + UNSCOPED_INFO("read as a person reads it"); + CHECK(ALScriptTheme::labelOf("ScriptBackground") == std::string("Background")); + UNSCOPED_INFO("a kind is not the editor's"); + CHECK_FALSE(ALScriptTheme::isEditorColor(names.back())); + UNSCOPED_INFO("a kind's name spaced"); + CHECK(ALScriptTheme::labelOf("ScriptDocComment") == std::string("Doc comment")); for (const std::string& name : names) { - ensure("a label for " + name, !ALScriptTheme::labelOf(name).empty()); + CAPTURE(name); + REQUIRE_FALSE(ALScriptTheme::labelOf(name).empty()); } } - template<> template<> - void alscripttheme_object::test<3>() + TEST_CASE_METHOD(alscripttheme_data, "alscripttheme: the chosen theme is read and written through the UI's setting", "[alscripttheme]") { - set_test_name("the theme chosen is the UI's setting, read and written through it"); if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } LLControlGroup* config = LLUI::getInstance()->mSettingGroups["config"]; if (!config->controlExists("ALScriptStudioTheme")) @@ -122,55 +119,80 @@ namespace tut ~ChosenBack() { ALScriptTheme::setChosen(was); } } chosen_back; ALScriptTheme::setChosen("Monokai"); - ensure_equals("the setting says it", config->getString("ALScriptStudioTheme"), std::string("Monokai")); - ensure_equals("and so does the theme", ALScriptTheme::chosen(), std::string("Monokai")); + UNSCOPED_INFO("the setting says it"); + CHECK(config->getString("ALScriptStudioTheme") == std::string("Monokai")); + UNSCOPED_INFO("and so does the theme"); + CHECK(ALScriptTheme::chosen() == std::string("Monokai")); ALScriptTheme::setChosen(std::string()); - ensure("none", ALScriptTheme::chosen().empty()); + REQUIRE(ALScriptTheme::chosen().empty()); } - template<> template<> - void alscripttheme_object::test<4>() + // What reads less than it should: code against both grounds, the text against its bands, the line numbers + // against the gutter. + TEST_CASE_METHOD(alscripttheme_data, "alscripttheme: illegible reports each colour too faint against its ground", "[alscripttheme]") { - set_test_name("what reads less than it should: code against both grounds, the text against its bands, the line numbers against the gutter"); ALScriptTheme theme; theme.colors["ScriptBackground"] = LLColor4(1.f, 1.f, 1.f, 1.f); theme.colors["ScriptText"] = LLColor4(0.f, 0.f, 0.f, 1.f); theme.colors["ScriptComment"] = LLColor4(0.2f, 0.4f, 0.2f, 1.f); - ensure("black and a dark green on white read", theme.illegible().empty()); + UNSCOPED_INFO("black and a dark green on white read"); + REQUIRE(theme.illegible().empty()); theme.colors["ScriptString"] = LLColor4(0.9f, 0.6f, 0.6f, 1.f); std::vector said = theme.illegible(); - ensure("a pale pink does not", said.size() == 1 && said[0].name == "ScriptString" && said[0].against == "ScriptBackground" && - said[0].contrast < ALSurface::LEGIBLE); + { + INFO("a pale pink does not"); + REQUIRE(said.size() == 1); + CHECK(said[0].name == "ScriptString"); + CHECK(said[0].against == "ScriptBackground"); + CHECK(said[0].contrast < ALSurface::LEGIBLE); + } theme.colors.erase("ScriptString"); theme.colors["ScriptBgReadOnlyColor"] = LLColor4(0.3f, 0.3f, 0.3f, 1.f); said = theme.illegible(); - ensure("a dark read-only ground takes both from the text", said.size() == 2 && said[0].against == "ScriptBgReadOnlyColor"); + { + INFO("a dark read-only ground takes both from the text"); + REQUIRE(said.size() == 2); + CHECK(said[0].against == "ScriptBgReadOnlyColor"); + } theme.colors.erase("ScriptBgReadOnlyColor"); theme.colors["ScriptSelectionColor"] = LLColor4(0.f, 0.f, 0.3f, 0.9f); said = theme.illegible(); - ensure("a selection the text is lost in, as laid over the ground", - said.size() == 1 && said[0].name == "ScriptText" && said[0].against == "ScriptSelectionColor"); + { + INFO("a selection the text is lost in, as laid over the ground"); + REQUIRE(said.size() == 1); + CHECK(said[0].name == "ScriptText"); + CHECK(said[0].against == "ScriptSelectionColor"); + } theme.colors["ScriptSelectionColor"] = LLColor4(0.f, 0.f, 0.3f, 0.2f); - ensure("thin enough, it reads", theme.illegible().empty()); + UNSCOPED_INFO("thin enough, it reads"); + REQUIRE(theme.illegible().empty()); theme.colors["ScriptGutterColor"] = LLColor4(0.95f, 0.95f, 0.95f, 1.f); theme.colors["ScriptLineNumberColor"] = LLColor4(0.75f, 0.75f, 0.75f, 1.f); said = theme.illegible(); - ensure("faint numbers on the gutter", said.size() == 1 && said[0].against == "ScriptGutterColor"); + { + INFO("faint numbers on the gutter"); + REQUIRE(said.size() == 1); + CHECK(said[0].against == "ScriptGutterColor"); + } theme.colors["ScriptLineNumberColor"] = LLColor4(0.5f, 0.5f, 0.5f, 1.f); - ensure("numbers are held to less than the code", theme.illegible().empty() && !theme.illegible(7.f, 4.5f).empty()); + { + INFO("numbers are held to less than the code"); + REQUIRE(theme.illegible().empty()); + REQUIRE_FALSE(theme.illegible(7.f, 4.5f).empty()); + } } - template<> template<> - void alscripttheme_object::test<5>() + // Every theme the viewer ships reads: the text and each kind of code at 4.5:1, the line numbers at 3:1; the + // high-contrast ones at 7 and 4.5. + TEST_CASE_METHOD(alscripttheme_data, "alscripttheme: every shipped theme reads at its contrast thresholds", "[alscripttheme]") { - set_test_name("every theme the viewer ships reads: the text and each kind of code at 4.5:1, the line numbers at 3:1; the high-contrast ones at 7 and 4.5"); if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } S32 high = 0; for (const ALScriptTheme& theme : ALScriptTheme::available()) @@ -179,6 +201,7 @@ namespace tut { continue; } + CAPTURE(theme.name); const bool contrast = theme.name.find("High Contrast") != std::string::npos; high += contrast; std::string said; @@ -186,28 +209,29 @@ namespace tut { said += "\n " + each.name + " on " + each.against + ": " + std::to_string(each.contrast); } - ensure(theme.name + " reads:" + said, said.empty()); + UNSCOPED_INFO("colours that read too faintly:" << said); + REQUIRE(said.empty()); } - ensure_equals("a dark and a light high-contrast theme", high, 2); + UNSCOPED_INFO("a dark and a light high-contrast theme"); + CHECK(high == 2); } - template<> template<> - void alscripttheme_object::test<6>() + TEST_CASE_METHOD(alscripttheme_data, "alscripttheme: every colour a theme speaks for has its label in strings.xml", "[alscripttheme]") { - set_test_name("every colour a theme speaks for has its label in strings.xml, to be translated"); #ifdef LLUI_TEST_APP_DIR std::ifstream in(std::string(LLUI_TEST_APP_DIR) + "/skins/default/xui/en/strings.xml"); if (!in) { - skip("no source tree: LLUI_TEST_APP_DIR does not point at newview"); + SKIP("no source tree: LLUI_TEST_APP_DIR does not point at newview"); } const std::string strings((std::istreambuf_iterator(in)), std::istreambuf_iterator()); for (const std::string& name : ALScriptTheme::names()) { - ensure("a label for " + name, strings.find("name=\"ScriptColorLabel_" + name + "\"") != std::string::npos); + CAPTURE(name); + REQUIRE(strings.find("name=\"ScriptColorLabel_" + name + "\"") != std::string::npos); } #else - skip("no LLUI_TEST_APP_DIR"); + SKIP("no LLUI_TEST_APP_DIR"); #endif } @@ -216,24 +240,24 @@ namespace tut // the caret's line and the selection, the line numbers against the // gutter -- the dark skins included, whose defaults were once the // white skin's. - template<> template<> - void alscripttheme_object::test<7>() + TEST_CASE_METHOD(alscripttheme_data, "alscripttheme: every shipped skin's script colours read and its studio parts differ", "[alscripttheme]") { #ifdef LLUI_TEST_APP_DIR const std::string skins = std::string(LLUI_TEST_APP_DIR) + "/skins/"; LLXMLNodePtr default_root; if (!LLXMLNode::parseFile(skins + "default/colors.xml", default_root, nullptr)) { - skip("no source tree: LLUI_TEST_APP_DIR does not point at newview"); + SKIP("no source tree: LLUI_TEST_APP_DIR does not point at newview"); } for (const char* skin : { "default", "alchemy", "gemini", "heretic", "ionic" }) { + CAPTURE(skin); ALColorSheet sheet; sheet.read(default_root, "default/colors.xml"); if (std::string(skin) != "default") { LLXMLNodePtr root; - ensure(std::string(skin) + " parses", LLXMLNode::parseFile(skins + skin + "/colors.xml", root, nullptr)); + REQUIRE(LLXMLNode::parseFile(skins + skin + "/colors.xml", root, nullptr)); sheet.read(root, std::string(skin) + "/colors.xml"); } sheet.resolve(); @@ -246,23 +270,26 @@ namespace tut { said += "\n " + each.name + " on " + each.against + ": " + std::to_string(each.contrast); } - ensure(std::string(skin) + "'s script colours read:" + said, said.empty()); + UNSCOPED_INFO("script colours that read too faintly:" << said); + REQUIRE(said.empty()); // And the studios' parts told apart by what they are: a divider // from the panes it divides, a tab not chosen from the chosen // one, the notice and the strips from the panes. - const auto apart = [&](const char* a, const char* b, F32 least) { + const auto require_apart = [&](const char* a, const char* b, F32 least) { + CAPTURE(a, b); const LLColor4* x = sheet.find(a); const LLColor4* y = sheet.find(b); - ensure(std::string(skin) + " has " + a + " and " + b, x && y); - ensure(std::string(skin) + ": " + a + " apart from " + b, ALSurface::contrast(*x, *y) >= least); + REQUIRE(x); + REQUIRE(y); + REQUIRE(ALSurface::contrast(*x, *y) >= least); }; - apart("StudioDividerColor", "StudioPaneColor", 1.3f); - apart("StudioTabColor", "PanelDefaultBackgroundColor", 1.08f); - apart("StudioNoticeColor", "StudioPaneColor", 1.08f); - apart("StudioStripColor", "StudioPaneColor", 1.05f); + require_apart("StudioDividerColor", "StudioPaneColor", 1.3f); + require_apart("StudioTabColor", "PanelDefaultBackgroundColor", 1.08f); + require_apart("StudioNoticeColor", "StudioPaneColor", 1.08f); + require_apart("StudioStripColor", "StudioPaneColor", 1.05f); } #else - skip("no LLUI_TEST_APP_DIR"); + SKIP("no LLUI_TEST_APP_DIR"); #endif } } diff --git a/indra/newview/tests/alscripttypes_test.cpp b/indra/newview/tests/alscripttypes_test.cpp index 4df0b597da..95f9f63ff9 100644 --- a/indra/newview/tests/alscripttypes_test.cpp +++ b/indra/newview/tests/alscripttypes_test.cpp @@ -27,9 +27,9 @@ #include "../alscripttypes.h" #include "alpreprocessor.h" #include "alsourcemap.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { struct alscripttypes_data { @@ -44,17 +44,9 @@ struct alscripttypes_data return out; } }; -typedef test_group alscripttypes_group; -typedef alscripttypes_group::object alscripttypes_object; -alscripttypes_group alscripttypes_test("alscripttypes"); -template<> -template<> -void alscripttypes_object::test<1>() +TEST_CASE_METHOD(alscripttypes_data, "alscripttypes: inSource maps diagnostics back through the envelope and the source map", "[alscripttypes]") { - set_test_name("a compile's diagnostics read back to the source: under the envelope's lines and through the map; an include's and " - "the preprocessor's own at no line, the include named; a client's envelope a line down, without columns; as said " - "with no map"); ALPreprocessor::Options options; options.fileName = "main.lsl"; options.resolve = [](const ALPreprocessor::Ask& ask, ALPreprocessor::Include& out) { @@ -63,7 +55,7 @@ void alscripttypes_object::test<1>() return ALPreprocessor::Found::Yes; }; const ALPreprocessor::Result expanded = ALPreprocessor::run("#include \"lib.lsl\"\ndefault\n{\nstate_entry() { x; }\n}\n", options); - ensure("expanded", expanded.problems.empty()); + REQUIRE(expanded.problems.empty()); // The lines of the expansion that hold each, and the column of `x`. S32 entry = -1, second = -1, column = -1, line = 0; for (size_t at = 0, next; at < expanded.text.size(); at = next + 1, ++line) @@ -81,7 +73,12 @@ void alscripttypes_object::test<1>() break; } } - ensure("found in the expansion", entry >= 0 && second >= 0 && column >= 0); + { + INFO("found in the expansion"); + CHECK(entry >= 0); + CHECK(second >= 0); + CHECK(column >= 0); + } ALScriptCompileResult result; result.codeLine = 7; @@ -95,20 +92,50 @@ void alscripttypes_object::test<1>() result.diagnostics.push_back(unplaced); const std::vector source = result.inSource(); - ensure_equals("four", source.size(), size_t(4)); - ensure("the script's own line and column", source[0].hasLine && source[0].line == 3 && source[0].hasColumn && source[0].column == 16 && - source[0].message == "undefined x"); - ensure("the include's at none, named with its line", !source[1].hasLine && source[1].message == "lib.lsl:2: b again"); - ensure("the preprocessor's own at none", !source[2].hasLine && source[2].message == "somewhere made"); - ensure("none stays none", !source[3].hasLine && source[3].message == "Math Error"); + REQUIRE(source.size() == size_t(4)); + { + INFO("the script's own line and column"); + CHECK(source[0].hasLine); + CHECK(source[0].line == 3); + CHECK(source[0].hasColumn); + CHECK(source[0].column == 16); + CHECK(source[0].message == "undefined x"); + } + { + INFO("the include's at none, named with its line"); + CHECK_FALSE(source[1].hasLine); + CHECK(source[1].message == "lib.lsl:2: b again"); + } + { + INFO("the preprocessor's own at none"); + CHECK_FALSE(source[2].hasLine); + CHECK(source[2].message == "somewhere made"); + } + { + INFO("none stays none"); + CHECK_FALSE(source[3].hasLine); + CHECK(source[3].message == "Math Error"); + } const std::vector wrapped = result.inSource(1); - ensure("a line down in an envelope, no column", wrapped[0].hasLine && wrapped[0].line == 4 && !wrapped[0].hasColumn); + { + INFO("a line down in an envelope, no column"); + CHECK(wrapped[0].hasLine); + CHECK(wrapped[0].line == 4); + CHECK_FALSE(wrapped[0].hasColumn); + } // Nothing expanded: as the compiler said it, the text having gone as it was. ALScriptCompileResult plain; plain.diagnostics.push_back(alscripttypes_data::said(4, 2, "as is")); const std::vector as_said = plain.inSource(1); - ensure("as said", as_said.size() == 1 && as_said[0].hasLine && as_said[0].line == 4 && as_said[0].hasColumn && as_said[0].column == 2); + { + INFO("as said"); + REQUIRE(as_said.size() == 1); + CHECK(as_said[0].hasLine); + CHECK(as_said[0].line == 4); + CHECK(as_said[0].hasColumn); + CHECK(as_said[0].column == 2); + } } -} // namespace tut +} // anonymous namespace diff --git a/indra/newview/tests/alscriptweightspane_test.cpp b/indra/newview/tests/alscriptweightspane_test.cpp index a25352ceea..11aec4dba9 100644 --- a/indra/newview/tests/alscriptweightspane_test.cpp +++ b/indra/newview/tests/alscriptweightspane_test.cpp @@ -36,14 +36,14 @@ #include "alscriptstudio_fixture.h" -#include "../test/lltut.h" +#include #include #include #include #include -namespace tut +namespace { struct alscriptweightspane_data { @@ -55,10 +55,11 @@ namespace tut LLPanel* tab() const { return floater ? floater->findChild("weights_tab", true) : nullptr; } // The tab as the skin built it, in its window's words. - ALScriptWeightsPane& weights() const + ALScriptWeightsPane& require_pane() const { ALScriptWeightsPane* pane = floater->findChild("weights_tab", true); - ensure("the skin builds the tab as the pane", pane != nullptr); + INFO("the skin builds the tab as the pane"); + REQUIRE(pane != nullptr); return *pane; } ALPaneList* list(const char* name) const { return floater ? floater->findChild(name, true) : nullptr; } @@ -133,91 +134,108 @@ namespace tut return shown; } }; - typedef test_group alscriptweightspane_group; - typedef alscriptweightspane_group::object alscriptweightspane_object; - alscriptweightspane_group alscriptweightspane_instance("ALScriptWeightsPane"); // The targets side by side, the script's own first and chosen; its // parts listed with their bytes and shares, in the viewer's words; a // target that does not compile says so rather than a number. - template<> template<> - void alscriptweightspane_object::test<1>() + TEST_CASE_METHOD(alscriptweightspane_data, "ALScriptWeightsPane: targets are listed with the script's own first and chosen", "[ALScriptWeightsPane]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } - ensure("the studio's window builds with its Weights tab", tab() != nullptr); - ALScriptWeightsPane& pane = weights(); + UNSCOPED_INFO("the studio's window builds with its Weights tab"); + REQUIRE(tab() != nullptr); + ALScriptWeightsPane& pane = require_pane(); pane.show(lsl()); ALPaneList* targets = list("weights_targets"); ALPaneList* parts = list("weights_parts"); - ensure_equals("three targets", targets->getItemCount(), 3); - ensure_equals("its own first", cell(targets->getAllData()[0], 0), std::string("LSL (LSO)")); - ensure_equals("its code", cell(targets->getAllData()[0], 1), std::string("4.0 KB")); - ensure_equals("its share", cell(targets->getAllData()[0], 2), std::string("25.0%")); - ensure_equals("an estimate said as one", cell(targets->getAllData()[1], 1), std::string("~5.9 KB")); - ensure_equals("one that does not compile has no number", cell(targets->getAllData()[2], 1), std::string("--")); - ensure("its own chosen", targets->getFirstSelected() && targets->getFirstSelected()->getValue().asInteger() == S32(ALScriptWeight::Target::LSO)); - - ensure_equals("its parts", parts->getItemCount(), 5); + REQUIRE(targets->getItemCount() == 3); + UNSCOPED_INFO("its own first"); + CHECK(cell(targets->getAllData()[0], 0) == std::string("LSL (LSO)")); + UNSCOPED_INFO("its code"); + CHECK(cell(targets->getAllData()[0], 1) == std::string("4.0 KB")); + UNSCOPED_INFO("its share"); + CHECK(cell(targets->getAllData()[0], 2) == std::string("25.0%")); + UNSCOPED_INFO("an estimate said as one"); + CHECK(cell(targets->getAllData()[1], 1) == std::string("~5.9 KB")); + UNSCOPED_INFO("one that does not compile has no number"); + CHECK(cell(targets->getAllData()[2], 1) == std::string("--")); + { + INFO("its own chosen"); + REQUIRE(targets->getFirstSelected()); + CHECK(targets->getFirstSelected()->getValue().asInteger() == S32(ALScriptWeight::Target::LSO)); + } + + CHECK(parts->getItemCount() == 5); const std::vector names = column(parts, 0); - ensure("the registers in words" + joined(names), std::find(names.begin(), names.end(), "Registers") != names.end()); - ensure("a handler in its state" + joined(names), std::find(names.begin(), names.end(), "touch_start in default") != names.end()); + UNSCOPED_INFO("the registers in words" + joined(names)); + CHECK(std::find(names.begin(), names.end(), "Registers") != names.end()); + UNSCOPED_INFO("a handler in its state" + joined(names)); + CHECK(std::find(names.begin(), names.end(), "touch_start in default") != names.end()); const std::vector lines = column(parts, 5); - ensure("an include's part at the include's line" + joined(lines), std::find(lines.begin(), lines.end(), "lib.lsl:1") != lines.end()); - ensure("the script's own at its line" + joined(lines), std::find(lines.begin(), lines.end(), "8") != lines.end()); + UNSCOPED_INFO("an include's part at the include's line" + joined(lines)); + CHECK(std::find(lines.begin(), lines.end(), "lib.lsl:1") != lines.end()); + UNSCOPED_INFO("the script's own at its line" + joined(lines)); + CHECK(std::find(lines.begin(), lines.end(), "8") != lines.end()); const std::string head = floater->findChild("weights_head", true)->getText(); - ensure("the head says whose and on what: " + head, head.find("Door") != std::string::npos && head.find("LSO") != std::string::npos); + { + INFO("the head says whose and on what: " + head); + CHECK(head.find("Door") != std::string::npos); + CHECK(head.find("LSO") != std::string::npos); + } } // Another target chosen lists its parts; a part chosen says where it // is, in the include where it is one's; one with no place says nothing. - template<> template<> - void alscriptweightspane_object::test<2>() + TEST_CASE_METHOD(alscriptweightspane_data, "ALScriptWeightsPane: another target chosen lists its parts and a part says where it is", "[ALScriptWeightsPane]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } - ALScriptWeightsPane& pane = weights(); + ALScriptWeightsPane& pane = require_pane(); pane.show(lsl()); ALPaneList* targets = list("weights_targets"); ALPaneList* parts = list("weights_parts"); parts->selectByValue(LLSD(2)); std::optional place = pane.chosenPlace(parts); - ensure("the include's function has a place", place.has_value()); - ensure_equals("in the include", place->file, std::string("disk:/lib.lsl")); - ensure_equals("by its name", place->fileName, std::string("lib.lsl")); - ensure_equals("at its line", place->line, 0); - ensure_equals("and column", place->column, 4); + UNSCOPED_INFO("the include's function has a place"); + REQUIRE(place.has_value()); + CHECK(place->file == std::string("disk:/lib.lsl")); + CHECK(place->fileName == std::string("lib.lsl")); + CHECK(place->line == 0); + CHECK(place->column == 4); parts->selectByValue(LLSD(0)); - ensure("the registers are nowhere", !pane.chosenPlace(parts)); + UNSCOPED_INFO("the registers are nowhere"); + CHECK_FALSE(pane.chosenPlace(parts)); targets->selectByValue(LLSD(S32(ALScriptWeight::Target::Mono))); targets->onCommit(); - ensure_equals("Mono's parts", parts->getItemCount(), 2); + UNSCOPED_INFO("Mono's parts"); + CHECK(parts->getItemCount() == 2); // The same script again keeps the target chosen; another starts at // its own. pane.show(lsl()); - ensure_equals("kept through a refill", parts->getItemCount(), 2); + UNSCOPED_INFO("kept through a refill"); + CHECK(parts->getItemCount() == 2); ALScriptWeightsPane::Shown other = lsl(); other.id = "script-2"; pane.show(other); - ensure_equals("another script at its own", parts->getItemCount(), 5); + UNSCOPED_INFO("another script at its own"); + CHECK(parts->getItemCount() == 5); } // Since the save: a part heavier or lighter by so much, one new since, // and the target's whole; nothing where it was not weighed then. - template<> template<> - void alscriptweightspane_object::test<3>() + TEST_CASE_METHOD(alscriptweightspane_data, "ALScriptWeightsPane: changes since the save show for each part and the target's whole", "[ALScriptWeightsPane]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } - ALScriptWeightsPane& pane = weights(); + ALScriptWeightsPane& pane = require_pane(); ALScriptWeightsPane::Shown shown = lsl(); ALScriptWeight saved = shown.weights[0]; saved.total = 4000; @@ -230,60 +248,67 @@ namespace tut pane.show(shown); ALPaneList* targets = list("weights_targets"); ALPaneList* parts = list("weights_parts"); - ensure_equals("the target's whole", cell(targets->getAllData()[0], 3), std::string("+96")); - ensure_equals("nothing for a target not weighed then", cell(targets->getAllData()[1], 3), std::string()); + UNSCOPED_INFO("the target's whole"); + CHECK(cell(targets->getAllData()[0], 3) == std::string("+96")); + UNSCOPED_INFO("nothing for a target not weighed then"); + CHECK(cell(targets->getAllData()[1], 3) == std::string()); const std::vector changes = column(parts, 4); - ensure_equals("the handler heavier" + joined(changes), changes[4], std::string("+24")); - ensure_equals("the function new" + joined(changes), changes[2], std::string("new")); - ensure_equals("the global the same where it moved" + joined(changes), changes[1], std::string()); + UNSCOPED_INFO("the handler heavier" + joined(changes)); + CHECK(changes[4] == std::string("+24")); + UNSCOPED_INFO("the function new" + joined(changes)); + CHECK(changes[2] == std::string("new")); + UNSCOPED_INFO("the global the same where it moved" + joined(changes)); + CHECK(changes[1] == std::string()); // Heaviest first, by the number and not the words. parts->sortByColumn("bytes", false); parts->updateSort(); - ensure_equals("the state first", cell(parts->getAllData()[0], 2), std::string("217")); - ensure_equals("the global last", cell(parts->getAllData()[4], 2), std::string("10")); + UNSCOPED_INFO("the state first"); + CHECK(cell(parts->getAllData()[0], 2) == std::string("217")); + UNSCOPED_INFO("the global last"); + CHECK(cell(parts->getAllData()[4], 2) == std::string("10")); parts->sortByColumn("change", false); parts->updateSort(); - ensure_equals("the new function moved most", cell(parts->getAllData()[0], 0), std::string("twice")); + UNSCOPED_INFO("the new function moved most"); + CHECK(cell(parts->getAllData()[0], 0) == std::string("twice")); // By name, as the names are shown. parts->sortByColumn("part", true); parts->updateSort(); const std::vector names = column(parts, 0); for (size_t i = 1; i < names.size(); ++i) { - ensure("in the order of their names" + joined(names), LLStringUtil::compareDict(names[i - 1], names[i]) <= 0); + UNSCOPED_INFO("in the order of their names" + joined(names)); + REQUIRE(LLStringUtil::compareDict(names[i - 1], names[i]) <= 0); } } - // Nothing to show says why, and holds nothing to choose. - template<> template<> - void alscriptweightspane_object::test<4>() + TEST_CASE_METHOD(alscriptweightspane_data, "ALScriptWeightsPane: showNothing says why and holds nothing to choose", "[ALScriptWeightsPane]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } - ALScriptWeightsPane& pane = weights(); + ALScriptWeightsPane& pane = require_pane(); pane.show(lsl()); pane.showNothing("Nothing open to weigh."); - ensure_equals("no targets", list("weights_targets")->getItemCount(), 0); - ensure_equals("no parts", list("weights_parts")->getItemCount(), 0); - ensure("nothing chosen", !pane.chosenPlace(pane.partsList())); - ensure_equals("why", floater->findChild("weights_head", true)->getText(), std::string("Nothing open to weigh.")); - ensure("and whose it is forgotten", pane.shownId().empty()); + CHECK(list("weights_targets")->getItemCount() == 0); + CHECK(list("weights_parts")->getItemCount() == 0); + CHECK_FALSE(pane.chosenPlace(pane.partsList())); + CHECK(floater->findChild("weights_head", true)->getText() == std::string("Nothing open to weigh.")); + UNSCOPED_INFO("and whose it is forgotten"); + CHECK(pane.shownId().empty()); } // Anonymous functions share an empty name: each is matched to the one // in its place among them then, not all to their sum. - template<> template<> - void alscriptweightspane_object::test<5>() + TEST_CASE_METHOD(alscriptweightspane_data, "ALScriptWeightsPane: anonymous functions are matched to the saved ones by their place", "[ALScriptWeightsPane]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } using Kind = ALScriptWeight::Part::Kind; - ALScriptWeightsPane& pane = weights(); + ALScriptWeightsPane& pane = require_pane(); ALScriptWeightsPane::Shown shown; shown.id = "script-3"; shown.name = "Handlers"; @@ -297,7 +322,8 @@ namespace tut shown.saved = { slua }; pane.show(shown); std::vector changes = column(list("weights_parts"), 4); - ensure_equals("nothing changed" + joined(changes), joined(changes), std::string("|||")); + UNSCOPED_INFO("nothing changed"); + CHECK(joined(changes) == std::string("|||")); // The middle one heavier, and one added after them. ALScriptWeight now = slua; now.parts[1].bytes = 250; @@ -305,19 +331,18 @@ namespace tut shown.weights = { now }; pane.show(shown); changes = column(list("weights_parts"), 4); - ensure_equals("the middle one, and one new" + joined(changes), joined(changes), std::string("||+50||new")); + UNSCOPED_INFO("the middle one, and one new"); + CHECK(joined(changes) == std::string("||+50||new")); } - template<> template<> - void alscriptweightspane_object::test<6>() + TEST_CASE_METHOD(alscriptweightspane_data, "ALScriptWeightsPane: an unnamed handler is named by its event and others said to be unnamed", "[ALScriptWeightsPane]") { - set_test_name("a function with no name of its own called by the event it handles, where the outline says so; else said to have none"); if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } using Kind = ALScriptWeight::Part::Kind; - ALScriptWeightsPane& pane = weights(); + ALScriptWeightsPane& pane = require_pane(); ALScriptWeightsPane::Shown shown; shown.id = "script-6"; shown.name = "Handlers"; @@ -331,26 +356,28 @@ namespace tut shown.handlers[0] = "touch_start"; pane.show(shown); const std::string names = joined(column(list("weights_parts"), 0)); - ensure("the handler by its event: " + names, names.find("touch_start") != std::string::npos); - ensure("the other with none: " + names, names.find(window.find("weights_tab")->getString("WeightsPartUnnamed")) != std::string::npos); + UNSCOPED_INFO("the handler by its event: " + names); + CHECK(names.find("touch_start") != std::string::npos); + UNSCOPED_INFO("the other with none: " + names); + CHECK(names.find(window.find("weights_tab")->getString("WeightsPartUnnamed")) != std::string::npos); } // What the region reserves for the script's object, where it has said: // after the code's size, for all of the object's scripts, with its URLs // where it has any; its time, where it told an estate manager; and the // tip saying how the region counts each. - template<> template<> - void alscriptweightspane_object::test<7>() + TEST_CASE_METHOD(alscriptweightspane_data, "ALScriptWeightsPane: the head says what the region reserves for the object where known", "[ALScriptWeightsPane]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } - ALScriptWeightsPane& pane = weights(); + ALScriptWeightsPane& pane = require_pane(); ALScriptWeightsPane::Shown shown = lsl(); LLTextBox* head = floater->findChild("weights_head", true); pane.show(shown); - ensure("nothing of the region", head->getText().find("region") == std::string::npos); + UNSCOPED_INFO("nothing of the region"); + CHECK(head->getText().find("region") == std::string::npos); ALScriptRegionUsage::Usage reserved; reserved.memory = 131072; reserved.when = LLDate(1.8e9); @@ -358,45 +385,64 @@ namespace tut shown.id = "script-2"; pane.show(shown); const std::string said = head->getText(); - ensure("reserved, in KB" + said, said.find("reserves 128 KB for all its scripts") != std::string::npos); - ensure("as of when" + said, said.find(ALRecoveryEntry::sayWhen(reserved.when)) != std::string::npos); - ensure("no URLs said where there are none" + said, said.find("URLs") == std::string::npos); - ensure("the tip says how it counts", head->getToolTip().find("64 KB for Mono") != std::string::npos); - ensure("and nothing of time unasked", head->getToolTip().find("Top Scripts") == std::string::npos); + UNSCOPED_INFO("reserved, in KB" + said); + CHECK(said.find("reserves 128 KB for all its scripts") != std::string::npos); + UNSCOPED_INFO("as of when" + said); + CHECK(said.find(ALRecoveryEntry::sayWhen(reserved.when)) != std::string::npos); + UNSCOPED_INFO("no URLs said where there are none" + said); + CHECK(said.find("URLs") == std::string::npos); + UNSCOPED_INFO("the tip says how it counts"); + CHECK(head->getToolTip().find("64 KB for Mono") != std::string::npos); + UNSCOPED_INFO("and nothing of time unasked"); + CHECK(head->getToolTip().find("Top Scripts") == std::string::npos); shown.region->urls = 3; shown.id = "script-3"; pane.show(shown); - ensure("and URLs where there are" + head->getText(), head->getText().find("128 KB and 3 URLs") != std::string::npos); - ensure("no time where the region said none" + head->getText(), head->getText().find("ms of the region") == std::string::npos); + UNSCOPED_INFO("and URLs where there are" + head->getText()); + CHECK(head->getText().find("128 KB and 3 URLs") != std::string::npos); + UNSCOPED_INFO("no time where the region said none" + head->getText()); + CHECK(head->getText().find("ms of the region") == std::string::npos); shown.region->time = 0.125f; shown.region->timeWhen = LLDate(1.8e9); shown.id = "script-4"; pane.show(shown); - ensure("its time, said to an estate manager" + head->getText(), head->getText().find("take 0.125 ms of the region's time") != std::string::npos); - ensure("the tip says how both are counted", head->getToolTip().find("64 KB for Mono") != std::string::npos && head->getToolTip().find("Top Scripts") != std::string::npos); + UNSCOPED_INFO("its time, said to an estate manager" + head->getText()); + CHECK(head->getText().find("take 0.125 ms of the region's time") != std::string::npos); + { + INFO("the tip says how both are counted"); + CHECK(head->getToolTip().find("64 KB for Mono") != std::string::npos); + CHECK(head->getToolTip().find("Top Scripts") != std::string::npos); + } ALScriptRegionUsage::Usage timed; timed.time = 0.5f; timed.timeWhen = LLDate(1.8e9); shown.region = timed; shown.id = "script-5"; pane.show(shown); - ensure("time alone: no memory said" + head->getText(), - head->getText().find("reserves") == std::string::npos && head->getText().find("take 0.500 ms") != std::string::npos); - ensure("and the tip of the time alone", head->getToolTip().find("64 KB for Mono") == std::string::npos && head->getToolTip().find("Top Scripts") != std::string::npos); + { + INFO("time alone: no memory said" + head->getText()); + CHECK(head->getText().find("reserves") == std::string::npos); + CHECK(head->getText().find("take 0.500 ms") != std::string::npos); + } + { + INFO("and the tip of the time alone"); + CHECK(head->getToolTip().find("64 KB for Mono") == std::string::npos); + CHECK(head->getToolTip().find("Top Scripts") != std::string::npos); + } } // LSL on Luau's compiler records no lines, so its lines are not // weighed one by one: said, where its heat would show nothing; not of // a target whose lines merely weigh nothing. - template<> template<> - void alscriptweightspane_object::test<8>() + TEST_CASE_METHOD(alscriptweightspane_data, "ALScriptWeightsPane: LSL on Luau's compiler is said to record no lines and LSO is not", "[ALScriptWeightsPane]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } - ensure("the studio's window builds with its Weights tab", tab() != nullptr); - ALScriptWeightsPane& pane = weights(); + UNSCOPED_INFO("the studio's window builds with its Weights tab"); + REQUIRE(tab() != nullptr); + ALScriptWeightsPane& pane = require_pane(); ALScriptWeightsPane::Shown shown = lsl(); shown.weights[2].compiled = true; shown.weights[2].error.clear(); @@ -404,24 +450,25 @@ namespace tut std::swap(shown.weights[0], shown.weights[2]); pane.show(shown); const std::string luau = floater->findChild("weights_head", true)->getText(); - ensure("said for LSL on Luau: " + luau, luau.find("records no lines") != std::string::npos); + UNSCOPED_INFO("said for LSL on Luau: " + luau); + CHECK(luau.find("records no lines") != std::string::npos); pane.show(lsl()); const std::string lso = floater->findChild("weights_head", true)->getText(); - ensure("not for LSO: " + lso, lso.find("records no lines") == std::string::npos); + UNSCOPED_INFO("not for LSO: " + lso); + CHECK(lso.find("records no lines") == std::string::npos); } // A Luau target's strings beside its parts: the starts several share // first, in italics, with what they would save; then each string, the // heaviest first, its uses, and a function's name said as one; each // chosen says where it is first named. No other target has a table. - template<> template<> - void alscriptweightspane_object::test<9>() + TEST_CASE_METHOD(alscriptweightspane_data, "ALScriptWeightsPane: a Luau target lists its strings beside its parts", "[ALScriptWeightsPane]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } - ALScriptWeightsPane& pane = weights(); + ALScriptWeightsPane& pane = require_pane(); ALScriptWeightsPane::Shown shown; shown.id = "script-2"; shown.name = "Greeter"; @@ -452,40 +499,73 @@ namespace tut pane.show(shown); ALPaneList* strings = list("weights_strings"); - ensure("the list there", strings && strings->getParent()->getVisible()); - ensure_equals("a start, then each string", strings->getItemCount(), 5); + { + INFO("the list there"); + REQUIRE(strings); + CHECK(strings->getParent()->getVisible()); + } + UNSCOPED_INFO("a start, then each string"); + REQUIRE(strings->getItemCount() == 5); const std::vector texts = column(strings, 0); - ensure("the start first: " + texts[0], texts[0].find("Shared start") == 0 && texts[0].find("\"You have touched me, \"") != std::string::npos && - texts[0].find("2 strings") != std::string::npos); - ensure_equals("what it saves, as less", column(strings, 1)[0], std::string("-9")); - ensure_equals("its strings' loads", column(strings, 2)[0], std::string("3")); - ensure_equals("at the first of them", column(strings, 4)[0], std::string("3")); - ensure_equals("then the heaviest first, a break shown as one", joined(texts).substr(joined({ texts[0] }).size()), + { + INFO("the start first: " + texts[0]); + CHECK(texts[0].find("Shared start") == 0); + CHECK(texts[0].find("\"You have touched me, \"") != std::string::npos); + CHECK(texts[0].find("2 strings") != std::string::npos); + } + UNSCOPED_INFO("what it saves, as less"); + CHECK(column(strings, 1)[0] == std::string("-9")); + UNSCOPED_INFO("its strings' loads"); + CHECK(column(strings, 2)[0] == std::string("3")); + UNSCOPED_INFO("at the first of them"); + CHECK(column(strings, 4)[0] == std::string("3")); + UNSCOPED_INFO("then the heaviest first, a break shown as one"); + CHECK(joined(texts).substr(joined({ texts[0] }).size()) == std::string("|\"You have touched me, alpha\"|\"You have touched me, bravo\"|\"greet\"|\"x\\ny\"")); - ensure_equals("each one's uses, a function's name said as one", joined(column(strings, 2)), std::string("|3|1|2|name|1")); + UNSCOPED_INFO("each one's uses, a function's name said as one"); + CHECK(joined(column(strings, 2)) == std::string("|3|1|2|name|1")); strings->selectByValue(LLSD(2)); std::optional place = pane.chosenPlace(strings); - ensure("a string chosen: where it is first named", place && place->file.empty() && place->line == 2); + { + INFO("a string chosen: where it is first named"); + REQUIRE(place); + CHECK(place->file.empty()); + CHECK(place->line == 2); + } strings->selectByValue(LLSD(0)); place = pane.chosenPlace(strings); - ensure("the start chosen: the first of its strings", place && place->line == 2); - ensure("not the parts' choice", !pane.chosenPlace(list("weights_parts"))); + { + INFO("the start chosen: the first of its strings"); + REQUIRE(place); + CHECK(place->line == 2); + } + UNSCOPED_INFO("not the parts' choice"); + CHECK_FALSE(pane.chosenPlace(list("weights_parts"))); // Kept chosen through a refill, by what it is. strings->selectByValue(LLSD(1)); pane.show(shown); - ensure("still chosen", strings->getFirstSelected() && strings->getFirstSelected()->getValue().asInteger() == 1); + { + INFO("still chosen"); + REQUIRE(strings->getFirstSelected()); + CHECK(strings->getFirstSelected()->getValue().asInteger() == 1); + } // A start's tip names its strings; each of them says it shares one. const auto tip = [&](S32 row) { const LLScrollListItem* item = strings->getAllData()[static_cast(row)]; return item->getColumn(0)->getToolTip(); }; - ensure("the start's strings named: " + tip(0), tip(0).find("\"You have touched me, alpha\"") != std::string::npos && - tip(0).find("\"You have touched me, bravo\"") != std::string::npos); - ensure("a string of it says so: " + tip(1), tip(1).find("shares its start") != std::string::npos); - ensure("one that is not, nothing: " + tip(4), tip(4).find("shares its start") == std::string::npos); + { + INFO("the start's strings named: " + tip(0)); + CHECK(tip(0).find("\"You have touched me, alpha\"") != std::string::npos); + CHECK(tip(0).find("\"You have touched me, bravo\"") != std::string::npos); + } + UNSCOPED_INFO("a string of it says so: " + tip(1)); + CHECK(tip(1).find("shares its start") != std::string::npos); + UNSCOPED_INFO("one that is not, nothing: " + tip(4)); + CHECK(tip(4).find("shares its start") == std::string::npos); // Since the save: a string not there then, new; a start by how its // saving moved, said as its bytes are, as less: saving more since, @@ -495,7 +575,8 @@ namespace tut then.sharedStarts[0].saved = 4; shown.saved = { then }; pane.show(shown); - ensure_equals("since the save", joined(column(strings, 3)), std::string("|-5||||new")); + UNSCOPED_INFO("since the save"); + CHECK(joined(column(strings, 3)) == std::string("|-5||||new")); // Keeping the start once asks the window, with its strings. std::string kept; @@ -506,25 +587,41 @@ namespace tut }); strings->selectByValue(LLSD(0)); floater->findChild("weights_keep_start", true)->onCommit(); - ensure("asked with the start and its strings", kept == "You have touched me, " && of.size() == 2 && of[1] == "You have touched me, bravo"); + { + INFO("asked with the start and its strings"); + CHECK(kept == "You have touched me, "); + REQUIRE(of.size() == 2); + CHECK(of[1] == "You have touched me, bravo"); + } kept.clear(); strings->selectByValue(LLSD(1)); floater->findChild("weights_keep_start", true)->onCommit(); - ensure("not for a string", kept.empty()); + UNSCOPED_INFO("not for a string"); + CHECK(kept.empty()); // Nor in a script that cannot be written to. pane.setKeepStart([&](const std::string& start, const std::vector&) { kept = start; }, []() { return false; }); strings->selectByValue(LLSD(0)); floater->findChild("weights_keep_start", true)->onCommit(); - ensure("not where the script cannot be written to", kept.empty()); + UNSCOPED_INFO("not where the script cannot be written to"); + CHECK(kept.empty()); // Nothing to show hides the list with the rest. pane.showNothing("Nothing open to weigh."); - ensure("hidden with nothing shown", !strings->getParent()->getVisible() && strings->getItemCount() == 0); + { + INFO("hidden with nothing shown"); + CHECK_FALSE(strings->getParent()->getVisible()); + CHECK(strings->getItemCount() == 0); + } pane.show(shown); - ensure("shown again", strings->getParent()->getVisible()); + UNSCOPED_INFO("shown again"); + CHECK(strings->getParent()->getVisible()); // LSO has no table: no list. pane.show(lsl()); - ensure("hidden for LSO", !strings->getParent()->getVisible() && strings->getItemCount() == 0); + { + INFO("hidden for LSO"); + CHECK_FALSE(strings->getParent()->getVisible()); + CHECK(strings->getItemCount() == 0); + } } } diff --git a/indra/newview/tests/alselectionoutline_gl_test.cpp b/indra/newview/tests/alselectionoutline_gl_test.cpp index ca5d9ac9d3..295e8f8388 100644 --- a/indra/newview/tests/alselectionoutline_gl_test.cpp +++ b/indra/newview/tests/alselectionoutline_gl_test.cpp @@ -42,7 +42,8 @@ #include "../../llrender/tests/llheadlessgl_fixture.h" -#include "../test/lltut.h" +#include +#include #include #include @@ -51,10 +52,8 @@ #include #include -namespace tut +namespace { - namespace - { using Outline = ALSelectionOutline; constexpr S32 W = 64; @@ -115,8 +114,6 @@ namespace tut return count; } - // Of the frame `px`, the sum of a column's coverage in red from row `y0` to `y1` - 1, in pixels: the width - // of a line across it. // The rows a column's runs of drawn pixels cover, bottom up, each as its first row and one past its last. std::vector> columnRuns(const std::vector& px, S32 x) { @@ -139,6 +136,8 @@ namespace tut return found; } + // Of the frame `px`, the sum of a column's coverage in red from row `y0` to `y1` - 1, in pixels: the width + // of a line across it. F32 ink(const std::vector& px, S32 x, S32 y0, S32 y1) { F32 sum = 0.f; @@ -228,11 +227,6 @@ namespace tut return reverse; } - static std::string named(bool reverse, const std::string& what) - { - return what + (reverse ? " (reverse-Z)" : " (forward)"); - } - void setConvention(bool reverse) { LLRender::sReverseZ = reverse; @@ -257,11 +251,12 @@ namespace tut std::map defines = { { "MAX_JOINTS_PER_MESH_OBJECT", "110" } }; for (const char* object : { "avatar/objectSkinV.glsl", "deferred/textureUtilV.glsl", "objects/nonindexedTextureV.glsl" }) { + CAPTURE(object); S32 level = 1; - ensure(std::string("compiled ") + object, mShaders.loadShaderFile(object, level, GL_VERTEX_SHADER, &defines) != 0); + REQUIRE(mShaders.loadShaderFile(object, level, GL_VERTEX_SHADER, &defines) != 0); } S32 level = 1; - ensure("compiled deferred/globalF.glsl", mShaders.loadShaderFile("deferred/globalF.glsl", level, GL_FRAGMENT_SHADER, &defines) != 0); + REQUIRE(mShaders.loadShaderFile("deferred/globalF.glsl", level, GL_FRAGMENT_SHADER, &defines) != 0); mIdProgram.mName = "Selection Id Shader"; mIdProgram.mShaderFiles.clear(); @@ -270,8 +265,9 @@ namespace tut mIdProgram.mShaderFiles.push_back(std::make_pair("interface/selectionAlphaF.glsl", GL_FRAGMENT_SHADER)); mIdProgram.mShaderFiles.push_back(std::make_pair("interface/selectionUtilF.glsl", GL_FRAGMENT_SHADER)); mIdProgram.mShaderLevel = 1; - ensure("id program builds with its rigged variant", mIdProgram.createShader(LLGLSLShader::VARIANT_RIGGED)); - ensure("id program has a rigged variant", mIdProgram.mRiggedVariant && mIdProgram.mRiggedVariant != &mIdProgram); + REQUIRE(mIdProgram.createShader(LLGLSLShader::VARIANT_RIGGED)); + REQUIRE(mIdProgram.mRiggedVariant); + REQUIRE(mIdProgram.mRiggedVariant != &mIdProgram); mWireProgram.mName = "Selection Wireframe Shader"; mWireProgram.mShaderFiles.clear(); @@ -281,8 +277,9 @@ namespace tut mWireProgram.mShaderFiles.push_back(std::make_pair("interface/selectionAlphaF.glsl", GL_FRAGMENT_SHADER)); mWireProgram.mShaderFiles.push_back(std::make_pair("interface/selectionUtilF.glsl", GL_FRAGMENT_SHADER)); mWireProgram.mShaderLevel = 1; - ensure("wireframe program builds with its rigged variant", mWireProgram.createShader(LLGLSLShader::VARIANT_RIGGED)); - ensure("wireframe program has a rigged variant", mWireProgram.mRiggedVariant && mWireProgram.mRiggedVariant != &mWireProgram); + REQUIRE(mWireProgram.createShader(LLGLSLShader::VARIANT_RIGGED)); + REQUIRE(mWireProgram.mRiggedVariant); + REQUIRE(mWireProgram.mRiggedVariant != &mWireProgram); // The final blit's depth copy, as LLViewerShaderMgr builds gCopyDepthProgram. mCopyDepthProgram.mName = "Copy Depth Shader"; @@ -293,7 +290,7 @@ namespace tut mCopyDepthProgram.addPermutation("COPY_DEPTH", "1"); mCopyDepthProgram.addPermutation("DEPTH_ONLY", "1"); mCopyDepthProgram.mShaderLevel = 1; - ensure("copy depth program builds", mCopyDepthProgram.createShader()); + REQUIRE(mCopyDepthProgram.createShader()); const std::pair passes[] = { { &mJumpProgram, "selectionJumpF.glsl" }, { &mTileProgram, "selectionTileF.glsl" }, @@ -318,31 +315,32 @@ namespace tut // The edge pass searching the disk for the glow too: the full search. program->addPermutation("GLOW_DISK", "1"); } - ensure(std::string(fragment) + "'s program builds", program->createShader()); + CAPTURE(fragment); + REQUIRE(program->createShader()); } const LLRenderTarget::eDepthFormat depth = mReverse ? LLRenderTarget::DEPTH_FMT_32F : LLRenderTarget::DEPTH_FMT_24; - ensure("id target", mIdMap.allocate(W, H, GL_RGBA8, true, false, ALTextureSlot::TT_TEXTURE, LLRenderTarget::MIPS_NONE, depth)); - ensure("jump target", mJumps.allocate(W, H, GL_R8)); - ensure("glow reach target", mGlowReach.allocate(Outline::tileCount(W), Outline::tileCount(H), GL_RGBA16)); - ensure("glow row target", mGlowRows.allocate(W, H, GL_R8)); - ensure("frame target", mFrame.allocate(W, H, GL_RGBA8)); - ensure("window target", mWindow.allocate(W, H, GL_RGBA8, true, false, ALTextureSlot::TT_TEXTURE, LLRenderTarget::MIPS_NONE, depth)); - ensure("tile target", mTiles.allocate(Outline::tileCount(W), Outline::tileCount(H), GL_RGBA16)); + REQUIRE(mIdMap.allocate(W, H, GL_RGBA8, true, false, ALTextureSlot::TT_TEXTURE, LLRenderTarget::MIPS_NONE, depth)); + REQUIRE(mJumps.allocate(W, H, GL_R8)); + REQUIRE(mGlowReach.allocate(Outline::tileCount(W), Outline::tileCount(H), GL_RGBA16)); + REQUIRE(mGlowRows.allocate(W, H, GL_R8)); + REQUIRE(mFrame.allocate(W, H, GL_RGBA8)); + REQUIRE(mWindow.allocate(W, H, GL_RGBA8, true, false, ALTextureSlot::TT_TEXTURE, LLRenderTarget::MIPS_NONE, depth)); + REQUIRE(mTiles.allocate(Outline::tileCount(W), Outline::tileCount(H), GL_RGBA16)); // Every tile holding every id and a jump: the edge pass searches every pixel's disk, as it did before // the tiles. - ensure("all tiles target", mAllTiles.allocate(Outline::tileCount(W), Outline::tileCount(H), GL_RGBA16)); + REQUIRE(mAllTiles.allocate(Outline::tileCount(W), Outline::tileCount(H), GL_RGBA16)); mAllTiles.bindTarget(); gGL.setClearColor(LLColor4(1.f, 1.f, 1.f, 1.f)); mAllTiles.clear(); gGL.setClearColor(LLColor4(0.f, 0.f, 0.f, 0.f)); mAllTiles.flush(); - ensure("far depth", mFar.allocate(W, H, 0, true, false, ALTextureSlot::TT_TEXTURE, LLRenderTarget::MIPS_NONE, depth)); + REQUIRE(mFar.allocate(W, H, 0, true, false, ALTextureSlot::TT_TEXTURE, LLRenderTarget::MIPS_NONE, depth)); clearDepth(mFar); sceneAt(W, H); mTriangle = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX); - ensure("triangle", mTriangle->allocateBuffer(3, 0)); + REQUIRE(mTriangle->allocateBuffer(3, 0)); LLStrider vert; mTriangle->getVertexStrider(vert); vert[0].set(-1.f, 1.f, 0.f); @@ -363,7 +361,7 @@ namespace tut void sceneAt(S32 width, S32 height) { const LLRenderTarget::eDepthFormat depth = mReverse ? LLRenderTarget::DEPTH_FMT_32F : LLRenderTarget::DEPTH_FMT_24; - ensure("scene depth", mScene.allocate(width, height, 0, true, false, ALTextureSlot::TT_TEXTURE, LLRenderTarget::MIPS_NONE, depth)); + REQUIRE(mScene.allocate(width, height, 0, true, false, ALTextureSlot::TT_TEXTURE, LLRenderTarget::MIPS_NONE, depth)); clearDepth(mScene); } @@ -406,7 +404,7 @@ namespace tut const U32 mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_COLOR | (weights ? LLVertexBuffer::MAP_WEIGHT4 : 0); LLPointer buffer = new LLVertexBuffer(mask); - ensure("quad", buffer->allocateBuffer(4, 6)); + REQUIRE(buffer->allocateBuffer(4, 6)); LLStrider vert; LLStrider texcoord; LLStrider index; @@ -626,7 +624,8 @@ namespace tut LLGLSLShader::unbind(); } target.flush(); - ensure("the id program reads the face's texture", texture_read); + UNSCOPED_INFO("the id program reads the face's texture"); + REQUIRE(texture_read); } void idPass(const std::vector& draws, const F32* palette = nullptr) @@ -855,7 +854,8 @@ namespace tut gGL.matrixMode(LLRender::MM_PROJECTION); gGL.loadMatrix(mProjection); gGL.matrixMode(LLRender::MM_MODELVIEW); - ensure("the wireframe program reads the face's texture", texture_read); + UNSCOPED_INFO("the wireframe program reads the face's texture"); + REQUIRE(texture_read); return px; } @@ -870,7 +870,8 @@ namespace tut { const LLVector4a extents[2] = { LLVector4a(-2.f, -1.f, QUAD_Z), LLVector4a(2.f, 1.f, QUAD_Z) }; LLRect rect; - ensure("the quads' box is on screen", Outline::scissorRect(mProjection, extents, W, H, reach, rect)); + UNSCOPED_INFO("the quads' box is on screen"); + REQUIRE(Outline::scissorRect(mProjection, extents, W, H, reach, rect)); return rect; } @@ -913,76 +914,106 @@ namespace tut bool mReverse = false; }; - typedef test_group alselectionoutline_t; - typedef alselectionoutline_t::object alselectionoutline_object_t; - tut::alselectionoutline_t alselectionoutline_testcase("ALSelectionOutline"); - } // Two objects side by side: each is outlined around its outer contour in its own colour, a solid line two // pixels wide at a UI scale of 1. The edge between them is drawn once, thin and faint, on the side of the one // given the lower id. Their insides and the space past the line are left alone. - template<> template<> - void alselectionoutline_object_t::test<1>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: two objects side by side are each outlined in their own colour", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); idPass({ { quadA(), ID_A }, { quadB(), ID_B } }); const std::vector ids = read(mIdMap); - ensure_equals(named(reverse, "A's id"), idAt(ids, 20, 32), ID_A); - ensure_equals(named(reverse, "B's id, past one byte"), idAt(ids, 40, 32), ID_B); - ensure_equals(named(reverse, "nothing selected outside"), idAt(ids, 2, 2), 0U); - ensure_equals(named(reverse, "the priority in alpha"), (U32)at(ids, 20, 32).a, (U32)Outline::PRIORITY_ROOT); + UNSCOPED_INFO("A's id"); + REQUIRE(idAt(ids, 20, 32) == ID_A); + UNSCOPED_INFO("B's id, past one byte"); + REQUIRE(idAt(ids, 40, 32) == ID_B); + UNSCOPED_INFO("nothing selected outside"); + REQUIRE(idAt(ids, 2, 2) == 0U); + UNSCOPED_INFO("the priority in alpha"); + REQUIRE((U32)at(ids, 20, 32).a == (U32)Outline::PRIORITY_ROOT); palette({ { ID_A, RED }, { ID_B, GREEN } }, true); const std::vector px = edgePass(); - ensure_equals(named(reverse, "A outlined at full strength beside its contour"), (U32)at(px, 8, 32).r, 255U); - ensure_equals(named(reverse, "and a pixel further"), (U32)at(px, 7, 32).r, 255U); - ensure(named(reverse, "and in its colour"), red(at(px, 7, 32))); - ensure(named(reverse, "nothing past the line"), black(at(px, 6, 32))); - ensure(named(reverse, "A outlined above"), red(at(px, 20, 45)) && black(at(px, 20, 46))); - ensure(named(reverse, "A outlined below"), red(at(px, 20, 18)) && black(at(px, 20, 17))); - ensure(named(reverse, "B outlined right of its contour"), green(at(px, 56, 32)) && black(at(px, 57, 32))); - ensure(named(reverse, "B outlined above"), green(at(px, 40, 45))); - ensure(named(reverse, "nothing far away"), black(at(px, 2, 2))); + UNSCOPED_INFO("A outlined at full strength beside its contour"); + REQUIRE((U32)at(px, 8, 32).r == 255U); + UNSCOPED_INFO("and a pixel further"); + REQUIRE((U32)at(px, 7, 32).r == 255U); + UNSCOPED_INFO("and in its colour"); + REQUIRE(red(at(px, 7, 32))); + UNSCOPED_INFO("nothing past the line"); + REQUIRE(black(at(px, 6, 32))); + { + INFO("A outlined above"); + REQUIRE(red(at(px, 20, 45))); + REQUIRE(black(at(px, 20, 46))); + } + { + INFO("A outlined below"); + REQUIRE(red(at(px, 20, 18))); + REQUIRE(black(at(px, 20, 17))); + } + { + INFO("B outlined right of its contour"); + REQUIRE(green(at(px, 56, 32))); + REQUIRE(black(at(px, 57, 32))); + } + UNSCOPED_INFO("B outlined above"); + REQUIRE(green(at(px, 40, 45))); + UNSCOPED_INFO("nothing far away"); + REQUIRE(black(at(px, 2, 2))); const Pixel edge = at(px, 31, 32); - ensure(named(reverse, "the shared edge on A's side, in A's colour"), red(edge)); - ensure(named(reverse, "fainter than the contour"), edge.r >= 150 && edge.r <= 156); - ensure(named(reverse, "and not on B's"), black(at(px, 32, 32))); - ensure(named(reverse, "A's inside left alone"), black(at(px, 20, 32))); - ensure(named(reverse, "B's inside left alone"), black(at(px, 44, 32))); + UNSCOPED_INFO("the shared edge on A's side, in A's colour"); + REQUIRE(red(edge)); + { + INFO("fainter than the contour"); + REQUIRE(edge.r >= 150); + REQUIRE(edge.r <= 156); + } + UNSCOPED_INFO("and not on B's"); + REQUIRE(black(at(px, 32, 32))); + UNSCOPED_INFO("A's inside left alone"); + REQUIRE(black(at(px, 20, 32))); + UNSCOPED_INFO("B's inside left alone"); + REQUIRE(black(at(px, 44, 32))); } } // The same two quads under one id, as a mask without ids would see them: the contour stays and the edge // between them is gone. This is what the ids buy. - template<> template<> - void alselectionoutline_object_t::test<2>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: two quads under one id keep their contour but lose the edge between", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); idPass({ { quadA(), ID_A }, { quadB(), ID_A } }); palette({ { ID_A, RED } }, true); const std::vector px = edgePass(); - ensure(named(reverse, "contour still outlined"), red(at(px, 8, 32))); - ensure(named(reverse, "contour still outlined on the far side"), red(at(px, 55, 32))); - ensure(named(reverse, "no edge between them on one side"), black(at(px, 31, 32))); - ensure(named(reverse, "no edge between them on the other"), black(at(px, 32, 32))); + UNSCOPED_INFO("contour still outlined"); + REQUIRE(red(at(px, 8, 32))); + UNSCOPED_INFO("contour still outlined on the far side"); + REQUIRE(red(at(px, 55, 32))); + UNSCOPED_INFO("no edge between them on one side"); + REQUIRE(black(at(px, 31, 32))); + UNSCOPED_INFO("no edge between them on the other"); + REQUIRE(black(at(px, 32, 32))); } } // Behind something nearer in the scene's depth, the surface is marked hidden and its outline drawn dimmed; // with hidden parts off, not at all. Where nothing covers it, the outline is unchanged. - template<> template<> - void alselectionoutline_object_t::test<3>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a surface behind the scene's depth is outlined dimmed or not at all", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); // An occluder three metres out over columns 0 to 19, which covers A's left part. @@ -998,42 +1029,55 @@ namespace tut idPass({ { quadA(), ID_A } }); std::vector ids = read(mIdMap); - ensure(named(reverse, "covered part marked hidden and drawn"), drawnHidden(at(ids, 12, 32))); - ensure_equals(named(reverse, "uncovered part marked visible"), (U32)at(ids, 25, 32).b, 255U); - ensure_equals(named(reverse, "hidden parts keep their id"), idAt(ids, 12, 32), ID_A); + UNSCOPED_INFO("covered part marked hidden and drawn"); + REQUIRE(drawnHidden(at(ids, 12, 32))); + UNSCOPED_INFO("uncovered part marked visible"); + REQUIRE((U32)at(ids, 25, 32).b == 255U); + UNSCOPED_INFO("hidden parts keep their id"); + REQUIRE(idAt(ids, 12, 32) == ID_A); palette({ { ID_A, RED } }, true); std::vector px = edgePass(); const U32 hidden = at(px, 8, 32).r; const U32 visible = at(px, 25, 44).r; - ensure_equals(named(reverse, "visible outline at full strength"), visible, 255U); - ensure(named(reverse, "hidden outline dimmed to the hidden alpha"), hidden >= 90 && hidden <= 115); + UNSCOPED_INFO("visible outline at full strength"); + REQUIRE(visible == 255U); + { + INFO("hidden outline dimmed to the hidden alpha"); + REQUIRE(hidden >= 90); + REQUIRE(hidden <= 115); + } Draw left_out = { quadA(), ID_A }; left_out.mShowHidden = false; idPass({ left_out }); ids = read(mIdMap); - ensure_equals(named(reverse, "covered part marked not drawn"), (U32)at(ids, 12, 32).b, 0U); - ensure_equals(named(reverse, "uncovered part still visible"), (U32)at(ids, 25, 32).b, 255U); + UNSCOPED_INFO("covered part marked not drawn"); + REQUIRE((U32)at(ids, 12, 32).b == 0U); + UNSCOPED_INFO("uncovered part still visible"); + REQUIRE((U32)at(ids, 25, 32).b == 255U); palette({ { ID_A, RED } }, false); px = edgePass(); - ensure(named(reverse, "hidden outline left out"), black(at(px, 8, 32))); - ensure_equals(named(reverse, "visible outline kept"), (U32)at(px, 25, 44).r, 255U); + UNSCOPED_INFO("hidden outline left out"); + REQUIRE(black(at(px, 8, 32))); + UNSCOPED_INFO("visible outline kept"); + REQUIRE((U32)at(px, 25, 44).r == 255U); } } // A surface that is itself in the scene is visible, at the window's resolution and at half of it, where the // scene's depth is read by uv from texels twice the size and, on a surface turned away, nearer than the // fragment by up to half a texel of its slope. - template<> template<> - void alselectionoutline_object_t::test<4>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a surface the scene drew is visible at full and at half resolution", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); for (S32 divisor : { 1, 2 }) { + CAPTURE(divisor); sceneAt(W / divisor, H / divisor); // Turned about 60 degrees from the eye: five metres deep across its width. LLVertexBuffer* tilted = quad(-1.5f, 1.5f, -1.f, 1.f, -3.5f, -8.5f); @@ -1056,66 +1100,81 @@ namespace tut } } } - const std::string at_divisor = " at render resolution / " + std::to_string(divisor); - ensure(named(reverse, "the surface covers pixels" + at_divisor), covered > 200); - ensure_equals(named(reverse, "none of its own pixels hidden by itself" + at_divisor), hidden, size_t(0)); + UNSCOPED_INFO("the surface covers pixels"); + REQUIRE(covered > 200); + UNSCOPED_INFO("none of its own pixels hidden by itself"); + REQUIRE(hidden == size_t(0)); } } } // The scissor is the objects' projected box grown by the contour's reach: the scissored edge pass draws exactly // what the unscissored one does. Without the growth the line outside the box is clipped. - template<> template<> - void alselectionoutline_object_t::test<5>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: the scissor is the objects' projected box grown by the contour's reach", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); idPass({ { quadA(), ID_A }, { quadB(), ID_B } }); palette({ { ID_A, RED }, { ID_B, GREEN } }, true); const std::vector full = edgePass(); const S32 reach = Outline::lineReach(view().mWidth); - ensure_equals(named(reverse, "the contour reaches its width and a pixel"), reach, 3); + UNSCOPED_INFO("the contour reaches its width and a pixel"); + REQUIRE(reach == 3); const LLRect rect = boxRect(reach); - ensure_equals(named(reverse, "left: column 8.57 floored, less the reach"), rect.mLeft, 8 - reach); - ensure_equals(named(reverse, "right: column 55.43 ceiled, plus the reach"), rect.mRight, 56 + reach); - ensure_equals(named(reverse, "bottom: row 20.28 floored, less the reach"), rect.mBottom, 20 - reach); - ensure_equals(named(reverse, "top: row 43.72 ceiled, plus the reach"), rect.mTop, 44 + reach); + UNSCOPED_INFO("left: column 8.57 floored, less the reach"); + REQUIRE(rect.mLeft == 8 - reach); + UNSCOPED_INFO("right: column 55.43 ceiled, plus the reach"); + REQUIRE(rect.mRight == 56 + reach); + UNSCOPED_INFO("bottom: row 20.28 floored, less the reach"); + REQUIRE(rect.mBottom == 20 - reach); + UNSCOPED_INFO("top: row 43.72 ceiled, plus the reach"); + REQUIRE(rect.mTop == 44 + reach); const std::vector scissored = edgePass(&rect); - ensure(named(reverse, "something is outlined"), lit(full) > 0); - ensure(named(reverse, "the scissored pass draws every outline pixel"), scissored == full); + UNSCOPED_INFO("something is outlined"); + REQUIRE(lit(full) > 0); + UNSCOPED_INFO("the scissored pass draws every outline pixel"); + REQUIRE(scissored == full); const LLRect tight = boxRect(0); const std::vector clipped = edgePass(&tight); - ensure(named(reverse, "a box without the reach clips the line"), lit(clipped) < lit(full)); - ensure(named(reverse, "the line's outer pixels are what it loses"), !black(at(full, 7, 32)) && black(at(clipped, 7, 32))); + UNSCOPED_INFO("a box without the reach clips the line"); + REQUIRE(lit(clipped) < lit(full)); + { + INFO("the line's outer pixels are what it loses"); + REQUIRE_FALSE(black(at(full, 7, 32))); + REQUIRE(black(at(clipped, 7, 32))); + } } } // Where the scissor gives up: a box reaching behind the eye takes the whole target, and one wholly behind it // touches nothing. - template<> template<> - void alselectionoutline_object_t::test<6>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: scissorRect takes the whole target for a box reaching behind the eye", "[ALSelectionOutline]") { const LLMatrix4a proj = al_perspective(FOV, 1.f, NEAR_PLANE, FORWARD_FAR); LLRect rect; const LLVector4a straddling[2] = { LLVector4a(-1.f, -1.f, -5.f), LLVector4a(1.f, 1.f, 2.f) }; - ensure("a box reaching behind the eye is drawn", Outline::scissorRect(proj, straddling, W, H, 3, rect)); - ensure("over the whole target", rect == LLRect(0, H, W, 0)); + UNSCOPED_INFO("a box reaching behind the eye is drawn"); + REQUIRE(Outline::scissorRect(proj, straddling, W, H, 3, rect)); + UNSCOPED_INFO("over the whole target"); + CHECK(rect == LLRect(0, H, W, 0)); const LLVector4a behind[2] = { LLVector4a(-1.f, -1.f, 2.f), LLVector4a(1.f, 1.f, 5.f) }; - ensure("a box behind the eye is not", !Outline::scissorRect(proj, behind, W, H, 3, rect)); + UNSCOPED_INFO("a box behind the eye is not"); + CHECK_FALSE(Outline::scissorRect(proj, behind, W, H, 3, rect)); const LLVector4a aside[2] = { LLVector4a(30.f, -1.f, -5.f), LLVector4a(32.f, 1.f, -5.f) }; - ensure("a box off to the side is not", !Outline::scissorRect(proj, aside, W, H, 3, rect)); + UNSCOPED_INFO("a box off to the side is not"); + CHECK_FALSE(Outline::scissorRect(proj, aside, W, H, 3, rect)); } // A rigged object's id lands where its joint palette puts it, not at its bind pose, and moves with the pose. - template<> template<> - void alselectionoutline_object_t::test<7>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a rigged object's id lands where its joint palette poses it", "[ALSelectionOutline]") { // Joint 0 moved 1.5 m along +x, then along -x. const F32 right[12] = { 1.f, 0.f, 0.f, 1.5f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f }; @@ -1123,68 +1182,98 @@ namespace tut for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); LLVertexBuffer* bound = quad(-0.5f, 0.5f, -0.5f, 0.5f, QUAD_Z, QUAD_Z, true); idPass({ { bound, ID_A } }, right); std::vector ids = read(mIdMap); - ensure_equals(named(reverse, "posed right"), idAt(ids, 49, 32), ID_A); - ensure_equals(named(reverse, "not at the bind pose"), idAt(ids, 32, 32), 0U); - ensure_equals(named(reverse, "not posed left"), idAt(ids, 14, 32), 0U); + UNSCOPED_INFO("posed right"); + REQUIRE(idAt(ids, 49, 32) == ID_A); + UNSCOPED_INFO("not at the bind pose"); + REQUIRE(idAt(ids, 32, 32) == 0U); + UNSCOPED_INFO("not posed left"); + REQUIRE(idAt(ids, 14, 32) == 0U); idPass({ { bound, ID_A } }, left); ids = read(mIdMap); - ensure_equals(named(reverse, "posed left"), idAt(ids, 14, 32), ID_A); - ensure_equals(named(reverse, "gone from the right"), idAt(ids, 49, 32), 0U); + UNSCOPED_INFO("posed left"); + REQUIRE(idAt(ids, 14, 32) == ID_A); + UNSCOPED_INFO("gone from the right"); + REQUIRE(idAt(ids, 49, 32) == 0U); // Posed, it covers columns 9 to 20 and rows 26 to 37. palette({ { ID_A, RED } }, true); const std::vector px = edgePass(); - ensure(named(reverse, "outlined above where it is posed"), red(at(px, 14, 39))); - ensure(named(reverse, "outlined below where it is posed"), red(at(px, 14, 24))); - ensure(named(reverse, "not where it is not"), black(at(px, 49, 39)) && black(at(px, 49, 24))); + UNSCOPED_INFO("outlined above where it is posed"); + REQUIRE(red(at(px, 14, 39))); + UNSCOPED_INFO("outlined below where it is posed"); + REQUIRE(red(at(px, 14, 24))); + { + INFO("not where it is not"); + REQUIRE(black(at(px, 49, 39))); + REQUIRE(black(at(px, 49, 24))); + } } } // The palette: an id's visible colour above its hidden one, PALETTE_WIDTH ids to a pair of rows; the hidden // one dimmed, or transparent with hidden parts off. - template<> template<> - void alselectionoutline_object_t::test<8>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: the palette holds PALETTE_WIDTH ids to a pair of visible and hidden rows", "[ALSelectionOutline]") { - ensure_equals("one pair of rows up to 255", Outline::paletteRows(255), 2U); - ensure_equals("two from 256", Outline::paletteRows(256), 4U); + UNSCOPED_INFO("one pair of rows up to 255"); + CHECK(Outline::paletteRows(255) == 2U); + UNSCOPED_INFO("two from 256"); + CHECK(Outline::paletteRows(256) == 4U); std::vector texels((size_t)Outline::PALETTE_WIDTH * 4 * 4, 0); Outline::writePalette(texels, 300, LLColor4(1.f, 0.5f, 0.f, 0.8f), true); const size_t visible = ((size_t)2 * Outline::PALETTE_WIDTH + 44) * 4; const size_t hidden = ((size_t)3 * Outline::PALETTE_WIDTH + 44) * 4; - ensure_equals("visible red", (U32)texels[visible], 255U); - ensure_equals("visible green", (U32)texels[visible + 1], 128U); - ensure_equals("visible alpha", (U32)texels[visible + 3], 204U); - ensure_equals("hidden keeps the colour", (U32)texels[hidden + 1], 128U); - ensure_equals("hidden alpha dimmed", (U32)texels[hidden + 3], (U32)ll_round(0.8f * Outline::HIDDEN_ALPHA * 255.f)); + UNSCOPED_INFO("visible red"); + CHECK((U32)texels[visible] == 255U); + UNSCOPED_INFO("visible green"); + CHECK((U32)texels[visible + 1] == 128U); + UNSCOPED_INFO("visible alpha"); + CHECK((U32)texels[visible + 3] == 204U); + UNSCOPED_INFO("hidden keeps the colour"); + CHECK((U32)texels[hidden + 1] == 128U); + UNSCOPED_INFO("hidden alpha dimmed"); + CHECK((U32)texels[hidden + 3] == (U32)ll_round(0.8f * Outline::HIDDEN_ALPHA * 255.f)); Outline::writePalette(texels, 300, LLColor4(1.f, 0.5f, 0.f, 0.8f), false); - ensure_equals("hidden transparent with hidden parts off", (U32)texels[hidden + 3], 0U); + UNSCOPED_INFO("hidden transparent with hidden parts off"); + CHECK((U32)texels[hidden + 3] == 0U); } // The widths: the contour AlchemySelectionOutlineWidth wide, held to 1 to 16, and the edges between objects a // pixel, at a UI scale of 1; both scaled with it and rounded, never under a pixel; the world and the HUD alike. - template<> template<> - void alselectionoutline_object_t::test<9>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: line widths scale with the UI and the contour's is held to 1 to 16", "[ALSelectionOutline]") { - ensure_equals("the setting's default", Outline::contourWidth(Outline::DEFAULT_CONTOUR_WIDTH, 1.f), 2); - ensure_equals("the default at 1.5", Outline::contourWidth(Outline::DEFAULT_CONTOUR_WIDTH, 1.5f), 3); - ensure_equals("the default at 2", Outline::contourWidth(Outline::DEFAULT_CONTOUR_WIDTH, 2.f), 4); - ensure_equals("a setting between pixels rounds", Outline::contourWidth(2.5f, 1.f), 3); - ensure_equals("a setting under 1 held to 1", Outline::contourWidth(0.25f, 1.f), 1); - ensure_equals("a setting over 16 held to 16", Outline::contourWidth(40.f, 1.f), 16); - ensure_equals("and scaled after", Outline::contourWidth(40.f, 2.f), 32); - ensure_equals("a setting that is not a number held to 1", Outline::contourWidth(std::numeric_limits::quiet_NaN(), 1.f), 1); - ensure_equals("never under a pixel", Outline::contourWidth(1.f, 0.2f), 1); - ensure_equals("edges at 1", Outline::lineWidth(Outline::INNER_WIDTH, 1.f), 1); - ensure_equals("edges at 2", Outline::lineWidth(Outline::INNER_WIDTH, 2.f), 2); - ensure_equals("a line reaches its width and a pixel", Outline::lineReach(4), 5); + UNSCOPED_INFO("the setting's default"); + CHECK(Outline::contourWidth(Outline::DEFAULT_CONTOUR_WIDTH, 1.f) == 2); + UNSCOPED_INFO("the default at 1.5"); + CHECK(Outline::contourWidth(Outline::DEFAULT_CONTOUR_WIDTH, 1.5f) == 3); + UNSCOPED_INFO("the default at 2"); + CHECK(Outline::contourWidth(Outline::DEFAULT_CONTOUR_WIDTH, 2.f) == 4); + UNSCOPED_INFO("a setting between pixels rounds"); + CHECK(Outline::contourWidth(2.5f, 1.f) == 3); + UNSCOPED_INFO("a setting under 1 held to 1"); + CHECK(Outline::contourWidth(0.25f, 1.f) == 1); + UNSCOPED_INFO("a setting over 16 held to 16"); + CHECK(Outline::contourWidth(40.f, 1.f) == 16); + UNSCOPED_INFO("and scaled after"); + CHECK(Outline::contourWidth(40.f, 2.f) == 32); + UNSCOPED_INFO("a setting that is not a number held to 1"); + CHECK(Outline::contourWidth(std::numeric_limits::quiet_NaN(), 1.f) == 1); + UNSCOPED_INFO("never under a pixel"); + CHECK(Outline::contourWidth(1.f, 0.2f) == 1); + UNSCOPED_INFO("edges at 1"); + CHECK(Outline::lineWidth(Outline::INNER_WIDTH, 1.f) == 1); + UNSCOPED_INFO("edges at 2"); + CHECK(Outline::lineWidth(Outline::INNER_WIDTH, 2.f) == 2); + UNSCOPED_INFO("a line reaches its width and a pixel"); + CHECK(Outline::lineReach(4) == 5); const LLMatrix4a hud = al_ortho(-0.5f, 0.5f, -0.5f, 0.5f, 0.f, 10.f); const LLMatrix4a world = al_perspective(FOV, 1.f, NEAR_PLANE, FORWARD_FAR); @@ -1194,23 +1283,29 @@ namespace tut { const Outline::View in_world = Outline::makeView(identity(), world, setting, ui_scale, false); const Outline::View on_hud = Outline::makeView(identity(), hud, setting, ui_scale, true); - const std::string with = " for " + std::to_string(setting) + " at UI scale " + std::to_string(ui_scale); - ensure_equals("the contour" + with, in_world.mWidth, Outline::contourWidth(setting, ui_scale)); - ensure_equals("the edges" + with, in_world.mInnerWidth, Outline::lineWidth(Outline::INNER_WIDTH, ui_scale)); - ensure_equals("the HUD's contour as the world's" + with, on_hud.mWidth, in_world.mWidth); - ensure_equals("the HUD's edges as the world's" + with, on_hud.mInnerWidth, in_world.mInnerWidth); - ensure("the HUD's view is the HUD's" + with, on_hud.mHUD && !in_world.mHUD); + CAPTURE(setting, ui_scale); + REQUIRE(in_world.mWidth == Outline::contourWidth(setting, ui_scale)); + REQUIRE(in_world.mInnerWidth == Outline::lineWidth(Outline::INNER_WIDTH, ui_scale)); + UNSCOPED_INFO("the HUD's contour as the world's"); + REQUIRE(on_hud.mWidth == in_world.mWidth); + UNSCOPED_INFO("the HUD's edges as the world's"); + REQUIRE(on_hud.mInnerWidth == in_world.mInnerWidth); + { + INFO("only the HUD's view is the HUD's"); + REQUIRE(on_hud.mHUD); + REQUIRE_FALSE(in_world.mHUD); + } } } } // On the HUD the scene's depth is not read: a surface behind something nearer, in a depth bound where the // scene's would be, still counts as visible, and its outline is drawn at full strength with hidden parts off. - template<> template<> - void alselectionoutline_object_t::test<10>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: the HUD's id pass does not read the scene's depth", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); // An occluder three metres out over the whole view, in front of A. @@ -1224,7 +1319,8 @@ namespace tut mScene.flush(); idPass({ { quadA(), ID_A } }); - ensure(named(reverse, "the world's pass hides A behind it"), drawnHidden(at(read(mIdMap), 20, 32))); + UNSCOPED_INFO("the world's pass hides A behind it"); + REQUIRE(drawnHidden(at(read(mIdMap), 20, 32))); idPass(mIdMap, nullptr, { { quadA(), ID_A } }, nullptr, &mScene); const std::vector ids = read(mIdMap); @@ -1241,12 +1337,15 @@ namespace tut } } } - ensure(named(reverse, "the HUD's pass draws A"), covered > 200); - ensure_equals(named(reverse, "and hides none of it"), hidden, size_t(0)); + UNSCOPED_INFO("the HUD's pass draws A"); + REQUIRE(covered > 200); + UNSCOPED_INFO("and hides none of it"); + REQUIRE(hidden == size_t(0)); palette({ { ID_A, RED } }, false); const std::vector px = edgePass(); - ensure_equals(named(reverse, "outlined at full strength"), (U32)at(px, 8, 32).r, 255U); + UNSCOPED_INFO("outlined at full strength"); + REQUIRE((U32)at(px, 8, 32).r == 255U); } } @@ -1254,8 +1353,7 @@ namespace tut // both conventions: al_ortho (gGL.ortho) reverses its depth with reverse-Z, and so does the flattened depth row // get_hud_matrices gives the HUD. An orthographic projection left unreversed under reverse-Z keeps the farthest // instead, which is what the reversal is for. - template<> template<> - void alselectionoutline_object_t::test<11>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: the HUD's projections keep the nearest surface in the id target's depth", "[ALSelectionOutline]") { constexpr F32 HUD_DEPTH = 10.f; constexpr U32 ID_NEAR = 1; @@ -1263,6 +1361,7 @@ namespace tut for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); // Columns and rows 19 to 44, and 6 to 57: the farther shows only around the nearer. LLVertexBuffer* nearer = quad(-0.2f, 0.2f, -0.2f, 0.2f, -2.f, -2.f); @@ -1275,15 +1374,17 @@ namespace tut const LLMatrix4a* projections[] = { &ortho, &flattened }; for (const LLMatrix4a* projection : projections) { - const std::string which = projection == &ortho ? "al_ortho" : "the HUD's flattened row"; + INFO((projection == &ortho ? "al_ortho" : "the HUD's flattened row")); for (bool near_first : { true, false }) { + CAPTURE(near_first); const std::vector ids = near_first ? projectedIds(*projection, { { nearer, ID_NEAR }, { farther, ID_FAR } }) : projectedIds(*projection, { { farther, ID_FAR }, { nearer, ID_NEAR } }); - const std::string order = near_first ? ", nearer drawn first" : ", farther drawn first"; - ensure_equals(named(reverse, which + ": the nearer kept" + order), idAt(ids, 32, 32), ID_NEAR); - ensure_equals(named(reverse, which + ": the farther around it" + order), idAt(ids, 10, 32), ID_FAR); + UNSCOPED_INFO("the nearer kept"); + REQUIRE(idAt(ids, 32, 32) == ID_NEAR); + UNSCOPED_INFO("the farther around it"); + REQUIRE(idAt(ids, 10, 32) == ID_FAR); } } @@ -1291,7 +1392,8 @@ namespace tut { const LLMatrix4a unreversed = LLMatrix4a::orthoZO(-0.5f, 0.5f, -0.5f, 0.5f, 0.f, HUD_DEPTH); const std::vector ids = projectedIds(unreversed, { { farther, ID_FAR }, { nearer, ID_NEAR } }); - ensure_equals(named(reverse, "unreversed, the farther wins"), idAt(ids, 32, 32), ID_FAR); + UNSCOPED_INFO("unreversed, the farther wins"); + REQUIRE(idAt(ids, 32, 32) == ID_FAR); } } } @@ -1299,18 +1401,18 @@ namespace tut // The contour is drawn as wide as the setting and the UI scale make it, held to the setting's range, and as many // pixels wide in the world, through a perspective with the scene's depth read, as on the HUD, through an // orthographic projection with none: its width is in pixels. - template<> template<> - void alselectionoutline_object_t::test<12>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: the drawn contour is as wide as contourWidth in the world and on the HUD", "[ALSelectionOutline]") { const std::pair settings[] = { { Outline::DEFAULT_CONTOUR_WIDTH, 1.f }, { Outline::DEFAULT_CONTOUR_WIDTH, 2.f }, { 4.f, 1.f }, { 1.f, 1.f }, { 2.5f, 1.f }, { 0.25f, 1.f }, { 40.f, 1.f } }; for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); palette({ { ID_A, RED } }, true); for (const auto& [setting, ui_scale] : settings) { - const std::string with = " for " + std::to_string(setting) + " at UI scale " + std::to_string((S32)ui_scale); + CAPTURE(setting, ui_scale); const S32 width = Outline::contourWidth(setting, ui_scale); // The HUD's: its left edge is column 20. @@ -1321,8 +1423,10 @@ namespace tut { ++hud_band; } - ensure_equals(named(reverse, "the HUD's contour is the setting's width" + with), hud_band, width); - ensure(named(reverse, "nothing past it" + with), black(at(px, 19 - width - 1, 32))); + UNSCOPED_INFO("the HUD's contour is the setting's width"); + REQUIRE(hud_band == width); + UNSCOPED_INFO("nothing past it"); + REQUIRE(black(at(px, 19 - width - 1, 32))); // The world's: A's left edge is column 9, room for a line eight pixels wide. if (width <= 8) @@ -1334,7 +1438,8 @@ namespace tut { ++world_band; } - ensure_equals(named(reverse, "the world's is the same" + with), world_band, hud_band); + UNSCOPED_INFO("the world's is the same"); + REQUIRE(world_band == hud_band); } } } @@ -1344,18 +1449,22 @@ namespace tut // through the base colour's transform and the face's texture animation; an opaque face is not cut at all, and // the face's colour, transparent or not, never is. The ramp's alpha is its column, and pixel column c samples // column 4c + 2 of it. - template<> template<> - void alselectionoutline_object_t::test<13>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a face's texture alpha cuts it out of the id target", "[ALSelectionOutline]") { - ensure_equals("a mask at its cutoff", Outline::legacyAlphaCutoff(true, LLMaterial::DIFFUSE_ALPHA_MODE_MASK, 64, true), 64.f / 255.f); - ensure_equals("a blend at the blend cutoff", Outline::legacyAlphaCutoff(true, LLMaterial::DIFFUSE_ALPHA_MODE_BLEND, 64, true), Outline::BLEND_ALPHA_CUTOFF); - ensure_equals("no material blends", Outline::legacyAlphaCutoff(false, 0, 0, true), Outline::BLEND_ALPHA_CUTOFF); - ensure_equals("opaque", Outline::legacyAlphaCutoff(true, LLMaterial::DIFFUSE_ALPHA_MODE_NONE, 64, true), Outline::NO_ALPHA_TEST); - ensure_equals("emissive", Outline::legacyAlphaCutoff(true, LLMaterial::DIFFUSE_ALPHA_MODE_EMISSIVE, 64, true), Outline::NO_ALPHA_TEST); - ensure_equals("a texture without alpha", Outline::legacyAlphaCutoff(true, LLMaterial::DIFFUSE_ALPHA_MODE_MASK, 64, false), Outline::NO_ALPHA_TEST); - ensure_equals("GLTF mask", Outline::gltfAlphaCutoff(LLGLTFMaterial::ALPHA_MODE_MASK, 0.25f), 0.25f); - ensure_equals("GLTF blend", Outline::gltfAlphaCutoff(LLGLTFMaterial::ALPHA_MODE_BLEND, 0.25f), Outline::BLEND_ALPHA_CUTOFF); - ensure_equals("GLTF opaque", Outline::gltfAlphaCutoff(LLGLTFMaterial::ALPHA_MODE_OPAQUE, 0.25f), Outline::NO_ALPHA_TEST); + UNSCOPED_INFO("a mask at its cutoff"); + CHECK(Outline::legacyAlphaCutoff(true, LLMaterial::DIFFUSE_ALPHA_MODE_MASK, 64, true) == 64.f / 255.f); + UNSCOPED_INFO("a blend at the blend cutoff"); + CHECK(Outline::legacyAlphaCutoff(true, LLMaterial::DIFFUSE_ALPHA_MODE_BLEND, 64, true) == Outline::BLEND_ALPHA_CUTOFF); + UNSCOPED_INFO("no material blends"); + CHECK(Outline::legacyAlphaCutoff(false, 0, 0, true) == Outline::BLEND_ALPHA_CUTOFF); + UNSCOPED_INFO("opaque"); + CHECK(Outline::legacyAlphaCutoff(true, LLMaterial::DIFFUSE_ALPHA_MODE_NONE, 64, true) == Outline::NO_ALPHA_TEST); + CHECK(Outline::legacyAlphaCutoff(true, LLMaterial::DIFFUSE_ALPHA_MODE_EMISSIVE, 64, true) == Outline::NO_ALPHA_TEST); + UNSCOPED_INFO("a texture without alpha"); + CHECK(Outline::legacyAlphaCutoff(true, LLMaterial::DIFFUSE_ALPHA_MODE_MASK, 64, false) == Outline::NO_ALPHA_TEST); + CHECK(Outline::gltfAlphaCutoff(LLGLTFMaterial::ALPHA_MODE_MASK, 0.25f) == 0.25f); + CHECK(Outline::gltfAlphaCutoff(LLGLTFMaterial::ALPHA_MODE_BLEND, 0.25f) == Outline::BLEND_ALPHA_CUTOFF); + CHECK(Outline::gltfAlphaCutoff(LLGLTFMaterial::ALPHA_MODE_OPAQUE, 0.25f) == Outline::NO_ALPHA_TEST); // Halved along u, as a GLTF transform and as a texture animation: column c samples 2c + 1. The transform is // packed as LLGLTFMaterial::TextureTransform::getPacked packs a scale of (0.5, 1). @@ -1369,6 +1478,7 @@ namespace tut for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); const U32 texture = ramp(); const F32 mask = Outline::legacyAlphaCutoff(true, LLMaterial::DIFFUSE_ALPHA_MODE_MASK, 64, true); @@ -1377,52 +1487,67 @@ namespace tut // Column 15 samples alpha 62, column 16 alpha 66. std::vector ids = pixelIds({ { pixels(0, W, 16, 48), ID_A, Outline::PRIORITY_ROOT, mask, texture } }); - ensure_equals(named(reverse, "a mask cuts under its cutoff"), idAt(ids, 15, 32), 0U); - ensure_equals(named(reverse, "and keeps over it"), idAt(ids, 16, 32), ID_A); - ensure_equals(named(reverse, "and keeps the opaque end"), idAt(ids, 63, 32), ID_A); + UNSCOPED_INFO("a mask cuts under its cutoff"); + REQUIRE(idAt(ids, 15, 32) == 0U); + UNSCOPED_INFO("and keeps over it"); + REQUIRE(idAt(ids, 16, 32) == ID_A); + UNSCOPED_INFO("and keeps the opaque end"); + REQUIRE(idAt(ids, 63, 32) == ID_A); // Column 5 samples alpha 22, column 6 alpha 26: either side of a tenth. ids = pixelIds({ { pixels(0, W, 16, 48), ID_A, Outline::PRIORITY_ROOT, blend, texture } }); - ensure_equals(named(reverse, "a blend cuts what is all but clear"), idAt(ids, 5, 32), 0U); - ensure_equals(named(reverse, "and keeps the faintest it draws"), idAt(ids, 6, 32), ID_A); + UNSCOPED_INFO("a blend cuts what is all but clear"); + REQUIRE(idAt(ids, 5, 32) == 0U); + UNSCOPED_INFO("and keeps the faintest it draws"); + REQUIRE(idAt(ids, 6, 32) == ID_A); ids = pixelIds({ { pixels(0, W, 16, 48), ID_A, Outline::PRIORITY_ROOT, opaque, texture } }); - ensure_equals(named(reverse, "an opaque face keeps its transparent texels"), idAt(ids, 0, 32), ID_A); - ensure_equals(named(reverse, "and the rest"), idAt(ids, 40, 32), ID_A); + UNSCOPED_INFO("an opaque face keeps its transparent texels"); + REQUIRE(idAt(ids, 0, 32) == ID_A); + UNSCOPED_INFO("and the rest"); + REQUIRE(idAt(ids, 40, 32) == ID_A); ids = pixelIds({ { pixels(0, W, 16, 48, &invisible), ID_A, Outline::PRIORITY_ROOT, mask, texture } }); - ensure_equals(named(reverse, "a transparent face colour cuts nothing more"), idAt(ids, 16, 32), ID_A); - ensure_equals(named(reverse, "and its texture still cuts"), idAt(ids, 15, 32), 0U); + UNSCOPED_INFO("a transparent face colour cuts nothing more"); + REQUIRE(idAt(ids, 16, 32) == ID_A); + UNSCOPED_INFO("and its texture still cuts"); + REQUIRE(idAt(ids, 15, 32) == 0U); // Column 20 samples alpha 41 halved, 82 not; column 40 81 halved. Draw transformed = { pixels(0, W, 16, 48), ID_A, Outline::PRIORITY_ROOT, mask, texture }; transformed.mTransform = &halved_pack; ids = pixelIds({ transformed }); - ensure_equals(named(reverse, "the base colour transform moves the cut"), idAt(ids, 20, 32), 0U); - ensure_equals(named(reverse, "and keeps past it"), idAt(ids, 40, 32), ID_A); + UNSCOPED_INFO("the base colour transform moves the cut"); + REQUIRE(idAt(ids, 20, 32) == 0U); + UNSCOPED_INFO("and keeps past it"); + REQUIRE(idAt(ids, 40, 32) == ID_A); Draw animated = { pixels(0, W, 16, 48), ID_A, Outline::PRIORITY_ROOT, mask, texture }; animated.mTextureMatrix = &halved_matrix; ids = pixelIds({ animated }); - ensure_equals(named(reverse, "the texture animation moves the cut"), idAt(ids, 20, 32), 0U); - ensure_equals(named(reverse, "and keeps past it"), idAt(ids, 40, 32), ID_A); + UNSCOPED_INFO("the texture animation moves the cut"); + REQUIRE(idAt(ids, 20, 32) == 0U); + UNSCOPED_INFO("and keeps past it"); + REQUIRE(idAt(ids, 40, 32) == ID_A); // A cut-out face shows what is behind it: here another object, which is outlined through the hole. ids = pixelIds({ { pixels(0, W, 16, 48), ID_A, Outline::PRIORITY_ROOT, mask, texture }, { quad(0.f, (F32)W, 0.f, (F32)H, PIXEL_Z - 1.f, PIXEL_Z - 1.f), ID_B } }); - ensure_equals(named(reverse, "the one behind shows through the cut"), idAt(ids, 15, 32), ID_B); - ensure_equals(named(reverse, "and not through the rest"), idAt(ids, 16, 32), ID_A); + UNSCOPED_INFO("the one behind shows through the cut"); + REQUIRE(idAt(ids, 15, 32) == ID_B); + UNSCOPED_INFO("and not through the rest"); + REQUIRE(idAt(ids, 16, 32) == ID_A); } } // The contour's anti-aliasing: across a diagonal edge, walking away from it along a row, the line's coverage // never rises, starts solid, takes values between and ends at nothing. Along a row the distance from a // 45-degree edge grows by sqrt(1/2) a pixel a pixel, so the walk takes twice the line's reach. - template<> template<> - void alselectionoutline_object_t::test<14>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: the contour's coverage falls smoothly away from a diagonal edge", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); // A diamond about (32.25, 32.25), 24 pixels to each point: its edge from (32.25, 8.25) to // (8.25, 32.25) runs through x = 40 - y, between pixel centres, so the pixels it covers are not left to @@ -1438,36 +1563,41 @@ namespace tut S32 between = 0; for (S32 y = 14; y <= 28; ++y) { - const std::string row = ", row " + std::to_string(y); + CAPTURE(y); S32 first = -1; for (S32 x = 0; x < W && first < 0; ++x) { first = idAt(ids, x, y) == ID_A ? x : -1; } - ensure_equals(named(reverse, "the diamond's edge" + row), first, 40 - y); + UNSCOPED_INFO("the diamond's edge"); + REQUIRE(first == 40 - y); U32 last = 255; for (S32 x = first - 1; x >= first - walk; --x) { const U32 r = at(px, x, y).r; - ensure(named(reverse, "coverage never rises away from the edge" + row), r <= last); + UNSCOPED_INFO("coverage never rises away from the edge"); + REQUIRE(r <= last); between += (r > 0 && r < 255) ? 1 : 0; last = r; } - ensure_equals(named(reverse, "solid beside the edge" + row), (U32)at(px, first - 1, y).r, 255U); - ensure_equals(named(reverse, "nothing past the reach" + row), last, 0U); + UNSCOPED_INFO("solid beside the edge"); + REQUIRE((U32)at(px, first - 1, y).r == 255U); + UNSCOPED_INFO("nothing past the reach"); + REQUIRE(last == 0U); } - ensure(named(reverse, "the edge is anti-aliased"), between >= 15); + UNSCOPED_INFO("the edge is anti-aliased"); + REQUIRE(between >= 15); } } // A feature a pixel across is outlined all the way round: every pixel in the disk the line covers is drawn, // including those no straight or diagonal line from the feature passes through. - template<> template<> - void alselectionoutline_object_t::test<15>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a feature a pixel across is outlined all the way round", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); pixelIds({ { pixels(32, 33, 32, 33), ID_A } }); palette({ { ID_A, RED } }, true); @@ -1485,23 +1615,28 @@ namespace tut } const F32 dist = sqrtf((F32)(dx * dx + dy * dy)); const U32 r = at(px, 32 + dx, 32 + dy).r; - const std::string where = " at (" + std::to_string(dx) + ", " + std::to_string(dy) + ")"; + CAPTURE(dx, dy); if (dist <= (F32)lines.mWidth) { - ensure_equals(named(reverse, "solid within the width" + where), r, 255U); + UNSCOPED_INFO("solid within the width"); + REQUIRE(r == 255U); } else if (dist < (F32)lines.mWidth + 0.9f) { - ensure(named(reverse, "anti-aliased past it" + where), r > 0 && r < 255); + INFO("anti-aliased past it"); + REQUIRE(r > 0); + REQUIRE(r < 255); } else if (dist >= (F32)reach) { - ensure_equals(named(reverse, "nothing at the reach" + where), r, 0U); + UNSCOPED_INFO("nothing at the reach"); + REQUIRE(r == 0U); } } } // Two across and one up: off every line of taps in eight directions. - ensure(named(reverse, "the knight's move is outlined"), at(px, 34, 33).r > 0); + UNSCOPED_INFO("the knight's move is outlined"); + REQUIRE(at(px, 34, 33).r > 0); } } @@ -1509,8 +1644,7 @@ namespace tut // Where a root and a child meet, the root's contour is laid over the child's, which shows under it only where // the root's fades; the edge between them is drawn on the root's side, a pixel wide, at INNER_OPACITY, and not // on the child's. - template<> template<> - void alselectionoutline_object_t::test<16>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: priorityOrder ranks ids and the root's contour is laid over the child's", "[ALSelectionOutline]") { const std::vector priorities = { Outline::PRIORITY_CHILD, Outline::PRIORITY_ROOT, Outline::PRIORITY_CONTEXT, Outline::PRIORITY_SUBTRACT, @@ -1518,34 +1652,61 @@ namespace tut Outline::PRIORITY_INSPECT }; std::vector order; Outline::priorityOrder(priorities, order); - ensure("the highest priority first, in the order added within one", order == std::vector({ 3, 5, 6, 1, 0, 4, 2 })); + UNSCOPED_INFO("the highest priority first, in the order added within one"); + CHECK(order == std::vector({ 3, 5, 6, 1, 0, 4, 2 })); constexpr U32 ROOT = 1; constexpr U32 CHILD = 2; for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); // The root over columns 16 to 31, the child 32 to 47, both rows 16 to 47. const std::vector ids = pixelIds({ { pixels(16, 32, 16, 48), ROOT, Outline::PRIORITY_ROOT }, { pixels(32, 48, 16, 48), CHILD, Outline::PRIORITY_CHILD } }); - ensure_equals(named(reverse, "the child's priority in alpha"), (U32)at(ids, 40, 32).a, (U32)Outline::PRIORITY_CHILD); + UNSCOPED_INFO("the child's priority in alpha"); + REQUIRE((U32)at(ids, 40, 32).a == (U32)Outline::PRIORITY_CHILD); palette({ { ROOT, RED }, { CHILD, GREEN } }, true); const std::vector px = edgePass(); // Above the child, a pixel from it and the root's corner a knight's move away. const Pixel over = at(px, 33, 48); - ensure(named(reverse, "the root's contour over the child's"), over.r > over.g && over.r > 200); - ensure(named(reverse, "the child's under it where it fades"), over.g > 0); - ensure(named(reverse, "the root's alone beside it"), red(at(px, 31, 48)) && at(px, 31, 48).r == 255); - ensure(named(reverse, "the child's alone past the root's reach"), green(at(px, 36, 48)) && at(px, 36, 48).g == 255); + { + INFO("the root's contour over the child's"); + REQUIRE(over.r > over.g); + REQUIRE(over.r > 200); + } + UNSCOPED_INFO("the child's under it where it fades"); + REQUIRE(over.g > 0); + { + INFO("the root's alone beside it"); + REQUIRE(red(at(px, 31, 48))); + REQUIRE(at(px, 31, 48).r == 255); + } + { + INFO("the child's alone past the root's reach"); + REQUIRE(green(at(px, 36, 48))); + REQUIRE(at(px, 36, 48).g == 255); + } const Pixel edge = at(px, 31, 32); const U32 faint = (U32)ll_round(255.f * Outline::INNER_OPACITY); - ensure(named(reverse, "the edge on the root's side, in its colour"), red(edge)); - ensure(named(reverse, "at INNER_OPACITY"), (U32)edge.r + 2 >= faint && (U32)edge.r <= faint + 2); - ensure(named(reverse, "a pixel wide, where the contour is two"), black(at(px, 30, 32))); - ensure(named(reverse, "and not on the child's side"), black(at(px, 32, 32))); - ensure(named(reverse, "the insides left alone"), black(at(px, 20, 32)) && black(at(px, 40, 32))); + UNSCOPED_INFO("the edge on the root's side, in its colour"); + REQUIRE(red(edge)); + { + INFO("at INNER_OPACITY"); + REQUIRE((U32)edge.r + 2 >= faint); + REQUIRE((U32)edge.r <= faint + 2); + } + UNSCOPED_INFO("a pixel wide, where the contour is two"); + REQUIRE(black(at(px, 30, 32))); + UNSCOPED_INFO("and not on the child's side"); + REQUIRE(black(at(px, 32, 32))); + { + INFO("the insides left alone"); + REQUIRE(black(at(px, 20, 32))); + REQUIRE(black(at(px, 40, 32))); + } } } @@ -1554,12 +1715,12 @@ namespace tut // inside another, both priorities, and lines from one pixel wide to sixteen, whose reach spans five tiles. A // change along a tile's left or lower border belongs to the tile beyond it, which flags it; a tile inside an // object flags nothing. - template<> template<> - void alselectionoutline_object_t::test<17>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: the tile pass's early out draws what searching every disk draws", "[ALSelectionOutline]") { const std::pair line_widths[] = { { 1, 1 }, { 2, 1 }, { 4, 1 }, { 8, 2 }, { 16, 2 } }; for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); palette({ { 1, RED }, { 2, GREEN }, { 3, LLColor4(0.f, 0.f, 1.f, 1.f) }, { 4, LLColor4(1.f, 1.f, 0.f, 0.5f) } }, true); @@ -1597,15 +1758,18 @@ namespace tut for (const auto& [name, draws] : scenes) { + INFO(name); pixelIds(draws); for (const auto& [width, inner_width] : line_widths) { + CAPTURE(width, inner_width); const Outline::View lines = widths(width, inner_width); - const std::string what = name + ", lines " + std::to_string(width) + " and " + std::to_string(inner_width); const std::vector untiled = edgePass(lines, nullptr, true); const std::vector tiled = edgePass(lines); - ensure(named(reverse, what + ": something is drawn"), lit(untiled) > 0); - ensure(named(reverse, what + ": tiled as untiled"), tiled == untiled); + UNSCOPED_INFO("something is drawn"); + REQUIRE(lit(untiled) > 0); + UNSCOPED_INFO("tiled as untiled"); + REQUIRE(tiled == untiled); } const std::vector records = tileRecords(); @@ -1614,7 +1778,8 @@ namespace tut { empty += records[i + 1] == 0 ? 1 : 0; } - ensure(named(reverse, name + ": some tiles hold nothing"), empty > 0); + UNSCOPED_INFO("some tiles hold nothing"); + REQUIRE(empty > 0); } // The first scene again, its edges on tile borders: a tile holds the ids of its own texels, and a tile @@ -1624,18 +1789,40 @@ namespace tut std::vector records = tileRecords(); const S32 columns = (S32)Outline::tileCount(W); auto record = [&](S32 column, S32 row, S32 i) { return (U32)records[((size_t)row * columns + column) * 4 + i]; }; - ensure(named(reverse, "the tile left of the root holds nothing"), record(1, 2, 0) == 0 && record(1, 2, 1) == 0); - ensure(named(reverse, "the tile inside the root holds the root alone"), record(2, 2, 0) == 1 && record(2, 2, 1) == 1); - ensure(named(reverse, "the tile inside the child beside it, the child alone"), record(4, 2, 0) == 2 && record(4, 2, 1) == 2); - ensure(named(reverse, "the tile under the child above, the root alone"), record(2, 4, 0) == 1 && record(2, 4, 1) == 1); - ensure(named(reverse, "no jump in it"), record(2, 2, 2) == 0); - ensure(named(reverse, "a tile far away holds nothing"), record(0, 0, 1) == 0); + { + INFO("the tile left of the root holds nothing"); + REQUIRE(record(1, 2, 0) == 0); + REQUIRE(record(1, 2, 1) == 0); + } + { + INFO("the tile inside the root holds the root alone"); + REQUIRE(record(2, 2, 0) == 1); + REQUIRE(record(2, 2, 1) == 1); + } + { + INFO("the tile inside the child beside it, the child alone"); + REQUIRE(record(4, 2, 0) == 2); + REQUIRE(record(4, 2, 1) == 2); + } + { + INFO("the tile under the child above, the root alone"); + REQUIRE(record(2, 4, 0) == 1); + REQUIRE(record(2, 4, 1) == 1); + } + UNSCOPED_INFO("no jump in it"); + REQUIRE(record(2, 2, 2) == 0); + UNSCOPED_INFO("a tile far away holds nothing"); + REQUIRE(record(0, 0, 1) == 0); // The second, across tile borders: the tile at column and row 3 holds both the root and the child. pixelIds(scenes[1].second); markPasses(view()); records = tileRecords(); - ensure(named(reverse, "a tile across the edge holds both"), record(3, 3, 0) == 1 && record(3, 3, 1) == 2); + { + INFO("a tile across the edge holds both"); + REQUIRE(record(3, 3, 0) == 1); + REQUIRE(record(3, 3, 1) == 2); + } // The tile pass over the tiles under a rect holding every selected texel and one more around them, as the // viewer runs it under its scissor, draws the same. @@ -1643,22 +1830,23 @@ namespace tut const LLRect around(12, 62, 54, 20); for (const auto& [width, inner_width] : line_widths) { + CAPTURE(width, inner_width); const Outline::View lines = widths(width, inner_width); - const std::string what = "under a rect, lines " + std::to_string(width); - ensure(named(reverse, what), edgePass(lines, nullptr, false, &around) == edgePass(lines, nullptr, true)); + UNSCOPED_INFO("the tiles under a rect draw as every tile does"); + REQUIRE(edgePass(lines, nullptr, false, &around) == edgePass(lines, nullptr, true)); } } } // Faces are culled as the scene culls them: a single-sided face seen from behind, which the scene does not draw, // takes no pixel from the object behind it, whose contour stays whole; a double-sided one is drawn, and outlined. - template<> template<> - void alselectionoutline_object_t::test<18>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: faces are culled as the scene culls them", "[ALSelectionOutline]") { constexpr U32 FRONT = 1; constexpr U32 REAR = 2; for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); palette({ { FRONT, RED }, { REAR, GREEN } }, true); // The rear over columns and rows 16 to 47; the front over 24 to 39, nearer, wound clockwise: its back. @@ -1668,30 +1856,42 @@ namespace tut LLVertexBuffer* front = quadAt(back_on); std::vector ids = pixelIds({ { rear, REAR, Outline::PRIORITY_CHILD }, { front, FRONT, Outline::PRIORITY_ROOT } }); - ensure_equals(named(reverse, "a single-sided face from behind takes no pixel"), idAt(ids, 32, 32), REAR); + UNSCOPED_INFO("a single-sided face from behind takes no pixel"); + REQUIRE(idAt(ids, 32, 32) == REAR); std::vector px = edgePass(); - ensure(named(reverse, "nothing traces it"), black(at(px, 23, 32)) && black(at(px, 24, 32)) && black(at(px, 40, 32))); - ensure_equals(named(reverse, "the rear's contour is whole"), (U32)at(px, 15, 32).g, 255U); + { + INFO("nothing traces it"); + REQUIRE(black(at(px, 23, 32))); + REQUIRE(black(at(px, 24, 32))); + REQUIRE(black(at(px, 40, 32))); + } + UNSCOPED_INFO("the rear's contour is whole"); + REQUIRE((U32)at(px, 15, 32).g == 255U); Draw double_sided = { front, FRONT, Outline::PRIORITY_ROOT }; double_sided.mDoubleSided = true; ids = pixelIds({ { rear, REAR, Outline::PRIORITY_CHILD }, double_sided }); - ensure_equals(named(reverse, "a double-sided face from behind is drawn"), idAt(ids, 32, 32), FRONT); + UNSCOPED_INFO("a double-sided face from behind is drawn"); + REQUIRE(idAt(ids, 32, 32) == FRONT); px = edgePass(); - ensure(named(reverse, "and outlined over the rear"), red(at(px, 23, 32)) && at(px, 23, 32).r == 255); + { + INFO("and outlined over the rear"); + REQUIRE(red(at(px, 23, 32))); + REQUIRE(at(px, 23, 32).r == 255); + } } } // A visible object in front of a selected one the scene hides is outlined in full over it: its contour neither // turns into an edge between the two nor goes on the hidden one's side. With hidden parts drawn, the visible // object's contour is still drawn at full strength, and the hidden one's own dimmed. - template<> template<> - void alselectionoutline_object_t::test<19>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a visible object in front of a hidden one is outlined in full over it", "[ALSelectionOutline]") { constexpr U32 ROOT = 1; constexpr U32 FROND = 2; for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); // The root, six metres out over columns 12 to 51 and rows 18 to 46, behind something five metres out; // the frond four metres out over columns and rows 25 to 38, in front of it. @@ -1701,26 +1901,46 @@ namespace tut for (bool show_hidden : { false, true }) { - const std::string hidden_parts = show_hidden ? ", hidden parts drawn" : ", hidden parts left out"; + CAPTURE(show_hidden); Draw hidden_root = { root, ROOT, Outline::PRIORITY_ROOT }; hidden_root.mShowHidden = show_hidden; idPass({ hidden_root, { frond, FROND, Outline::PRIORITY_CHILD } }); palette({ { ROOT, RED }, { FROND, GREEN } }, show_hidden); const std::vector px = edgePass(); - ensure(named(reverse, "the frond's contour over the root" + hidden_parts), - green(at(px, 24, 32)) && at(px, 24, 32).g == 255 && green(at(px, 23, 32)) && at(px, 23, 32).g == 255); - ensure(named(reverse, "above it too" + hidden_parts), green(at(px, 32, 39)) && at(px, 32, 39).g == 255); - ensure(named(reverse, "as wide as a contour" + hidden_parts), black(at(px, 21, 32))); - ensure(named(reverse, "nothing inside the frond" + hidden_parts), black(at(px, 32, 32)) && black(at(px, 25, 32))); + { + INFO("the frond's contour over the root"); + REQUIRE(green(at(px, 24, 32))); + REQUIRE(at(px, 24, 32).g == 255); + REQUIRE(green(at(px, 23, 32))); + REQUIRE(at(px, 23, 32).g == 255); + } + { + INFO("above it too"); + REQUIRE(green(at(px, 32, 39))); + REQUIRE(at(px, 32, 39).g == 255); + } + UNSCOPED_INFO("as wide as a contour"); + REQUIRE(black(at(px, 21, 32))); + { + INFO("nothing inside the frond"); + REQUIRE(black(at(px, 32, 32))); + REQUIRE(black(at(px, 25, 32))); + } if (show_hidden) { const U32 dimmed = at(px, 11, 32).r; - ensure(named(reverse, "the hidden root's contour dimmed"), red(at(px, 11, 32)) && dimmed >= 90 && dimmed <= 115); + { + INFO("the hidden root's contour dimmed"); + REQUIRE(red(at(px, 11, 32))); + REQUIRE(dimmed >= 90); + REQUIRE(dimmed <= 115); + } } else { - ensure(named(reverse, "the hidden root's contour left out"), black(at(px, 11, 32))); + UNSCOPED_INFO("the hidden root's contour left out"); + REQUIRE(black(at(px, 11, 32))); } } } @@ -1729,12 +1949,12 @@ namespace tut // A child in front of another draws its whole contour over it; two surfaces that touch on one plane, here a // wall seen at a slant, whose depth changes by some percent a pixel, draw only the thin edge between them, on // the side of the higher priority. - template<> template<> - void alselectionoutline_object_t::test<20>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a child in front of another draws its whole contour over it", "[ALSelectionOutline]") { const LLColor4 BLUE(0.f, 0.f, 1.f, 1.f); for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); // Six metres out over columns 12 to 51, and four metres out over columns and rows 25 to 38. @@ -1743,9 +1963,19 @@ namespace tut palette({ { 2, GREEN }, { 3, BLUE } }, true); std::vector px = edgePass(); const Pixel over = at(px, 24, 32); - ensure(named(reverse, "the front child's contour over the rear"), over.b == 255 && over.r == 0 && over.g == 0); - ensure(named(reverse, "its full width"), at(px, 23, 32).b == 255 && at(px, 32, 39).b == 255); - ensure(named(reverse, "nothing inside it"), black(at(px, 32, 32))); + { + INFO("the front child's contour over the rear"); + REQUIRE(over.b == 255); + REQUIRE(over.r == 0); + REQUIRE(over.g == 0); + } + { + INFO("its full width"); + REQUIRE(at(px, 23, 32).b == 255); + REQUIRE(at(px, 32, 39).b == 255); + } + UNSCOPED_INFO("nothing inside it"); + REQUIRE(black(at(px, 32, 32))); // A wall from four metres out at the left to twelve at the right, split at eight, columns 31 and 32. idPass({ { quad(-1.6f, 0.f, -1.f, 1.f, -4.f, -8.f), ID_A, Outline::PRIORITY_ROOT }, @@ -1754,22 +1984,32 @@ namespace tut px = edgePass(); const Pixel edge = at(px, 31, 32); const U32 faint = (U32)ll_round(255.f * Outline::INNER_OPACITY); - ensure(named(reverse, "the edge on the root's side"), red(edge) && (U32)edge.r + 2 >= faint && (U32)edge.r <= faint + 2); - ensure(named(reverse, "no contour on the child's side"), black(at(px, 32, 32)) && black(at(px, 33, 32))); - ensure(named(reverse, "none on the root's"), black(at(px, 30, 32))); + { + INFO("the edge on the root's side"); + REQUIRE(red(edge)); + REQUIRE((U32)edge.r + 2 >= faint); + REQUIRE((U32)edge.r <= faint + 2); + } + { + INFO("no contour on the child's side"); + REQUIRE(black(at(px, 32, 32))); + REQUIRE(black(at(px, 33, 32))); + } + UNSCOPED_INFO("none on the root's"); + REQUIRE(black(at(px, 30, 32))); } } // A selected object behind one the scene hides, its hidden parts left out, does not trace the hidden one's shape: // a frond behind a rock, in front of the visible root. And an object not drawn neither draws nor masks the // contour of one that is, though it is the nearer and of the higher priority. - template<> template<> - void alselectionoutline_object_t::test<21>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a hidden object with hidden parts left out traces and masks nothing", "[ALSelectionOutline]") { constexpr U32 ROOT = 1; constexpr U32 FROND = 2; for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); // The root six metres out over columns 12 to 51; the frond five metres out over columns and rows 26 to // 37, behind a rock four and a half metres out over exactly those. @@ -1778,15 +2018,20 @@ namespace tut frond.mShowHidden = false; idPass({ { quad(-2.f, 2.f, -1.5f, 1.5f, -6.f, -6.f), ROOT, Outline::PRIORITY_ROOT }, frond }); const std::vector ids = read(mIdMap); - ensure_equals(named(reverse, "the frond is over the root"), idAt(ids, 32, 32), FROND); - ensure_equals(named(reverse, "and not drawn"), (U32)at(ids, 32, 32).b, 0U); + UNSCOPED_INFO("the frond is over the root"); + REQUIRE(idAt(ids, 32, 32) == FROND); + UNSCOPED_INFO("and not drawn"); + REQUIRE((U32)at(ids, 32, 32).b == 0U); palette({ { ROOT, RED }, { FROND, GREEN } }, false); std::vector px = edgePass(); for (const auto& [x, y] : { std::pair{ 25, 32 }, { 24, 32 }, { 38, 32 }, { 39, 32 }, { 32, 25 }, { 32, 38 }, { 31, 31 } }) { - ensure(named(reverse, "nothing traces the frond at (" + std::to_string(x) + ", " + std::to_string(y) + ")"), black(at(px, x, y))); + CAPTURE(x, y); + UNSCOPED_INFO("nothing traces the frond"); + REQUIRE(black(at(px, x, y))); } - ensure_equals(named(reverse, "the root's own contour drawn"), (U32)at(px, 11, 32).r, 255U); + UNSCOPED_INFO("the root's own contour drawn"); + REQUIRE((U32)at(px, 11, 32).r == 255U); // A root five metres out over columns 9 to 31, hidden, and a child beside it over 32 to 54, visible, both // rows 21 to 43. Above the root, a pixel from it and a knight's move from the child. @@ -1798,14 +2043,24 @@ namespace tut palette({ { ROOT, RED }, { FROND, GREEN } }, false); px = edgePass(); const Pixel above = at(px, 30, 44); - ensure(named(reverse, "the child's contour where the hidden root is nearer"), green(above) && above.g > 200); + { + INFO("the child's contour where the hidden root is nearer"); + REQUIRE(green(above)); + REQUIRE(above.g > 200); + } // The two touch: the edge between them goes on the drawn side, the child's, though the root's is the // higher priority. const Pixel edge = at(px, 32, 32); const U32 faint = (U32)ll_round(255.f * Outline::INNER_OPACITY); - ensure(named(reverse, "the edge on the drawn side"), green(edge) && (U32)edge.g + 2 >= faint && (U32)edge.g <= faint + 2); - ensure(named(reverse, "not on the hidden root's"), black(at(px, 31, 32))); + { + INFO("the edge on the drawn side"); + REQUIRE(green(edge)); + REQUIRE((U32)edge.g + 2 >= faint); + REQUIRE((U32)edge.g <= faint + 2); + } + UNSCOPED_INFO("not on the hidden root's"); + REQUIRE(black(at(px, 31, 32))); } } @@ -1813,11 +2068,11 @@ namespace tut // step, and the edge pass draws the near part's whole contour over the far part from it, as another object's, // and nothing inside the near part. A gap of three percent of the distance is a jump too. The tile holding the // near side records it. - template<> template<> - void alselectionoutline_object_t::test<22>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: an object lying over itself marks the jump and outlines its near part", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); palette({ { ID_A, RED } }, true); // The far part six metres out over columns 12 to 51; the near part over columns and rows 25 to 38 at @@ -1825,17 +2080,37 @@ namespace tut for (F32 near_z : { -4.f, -5.8f }) { const F32 half = 0.125f * -near_z; - const std::string at_z = " at " + std::to_string(-near_z) + " m"; + CAPTURE(near_z); idPass({ { quad(-2.f, 2.f, -1.5f, 1.5f, -6.f, -6.f), ID_A }, { quad(-half, half, -half, half, near_z, near_z), ID_A } }); const std::vector px = edgePass(); - ensure(named(reverse, "the near side of the step marked" + at_z), markedAt(25, 32) && markedAt(32, 38)); - ensure(named(reverse, "not the far side, nor inside" + at_z), !markedAt(24, 32) && !markedAt(32, 32)); + { + INFO("the near side of the step marked"); + REQUIRE(markedAt(25, 32)); + REQUIRE(markedAt(32, 38)); + } + { + INFO("not the far side, nor inside"); + REQUIRE_FALSE(markedAt(24, 32)); + REQUIRE_FALSE(markedAt(32, 32)); + } - ensure(named(reverse, "the near part's contour over the far part" + at_z), - red(at(px, 24, 32)) && at(px, 24, 32).r == 255 && at(px, 23, 32).r == 255 && at(px, 32, 39).r == 255); - ensure(named(reverse, "as wide as a contour" + at_z), black(at(px, 21, 32))); - ensure(named(reverse, "nothing inside the near part" + at_z), black(at(px, 25, 32)) && black(at(px, 26, 32)) && black(at(px, 32, 32))); - ensure(named(reverse, "nothing over the far part away from it" + at_z), black(at(px, 15, 32))); + { + INFO("the near part's contour over the far part"); + REQUIRE(red(at(px, 24, 32))); + REQUIRE(at(px, 24, 32).r == 255); + REQUIRE(at(px, 23, 32).r == 255); + REQUIRE(at(px, 32, 39).r == 255); + } + UNSCOPED_INFO("as wide as a contour"); + REQUIRE(black(at(px, 21, 32))); + { + INFO("nothing inside the near part"); + REQUIRE(black(at(px, 25, 32))); + REQUIRE(black(at(px, 26, 32))); + REQUIRE(black(at(px, 32, 32))); + } + UNSCOPED_INFO("nothing over the far part away from it"); + REQUIRE(black(at(px, 15, 32))); } // The near part's edges on tile borders: the tiles holding them record a jump, a tile of the far part alone @@ -1844,19 +2119,21 @@ namespace tut markPasses(view()); const std::vector records = tileRecords(); const S32 columns = (S32)Outline::tileCount(W); - ensure(named(reverse, "a tile on the near side's edge records the jump"), records[((size_t)3 * columns + 3) * 4 + 2] != 0); - ensure(named(reverse, "a tile of the far part alone none"), records[((size_t)1 * columns + 1) * 4 + 2] == 0); + UNSCOPED_INFO("a tile on the near side's edge records the jump"); + REQUIRE(records[((size_t)3 * columns + 3) * 4 + 2] != 0); + UNSCOPED_INFO("a tile of the far part alone none"); + REQUIRE(records[((size_t)1 * columns + 1) * 4 + 2] == 0); } } // A surface bending, not stepping, marks no jump and draws nothing across itself: one mesh's inside corner at // 30, 45 and 60 degrees to the view, the outside and the inside of a cylinder out to their rims, and a plane // whose depth changes by three to five percent a pixel, in one piece and in two. - template<> template<> - void alselectionoutline_object_t::test<23>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a bending surface marks no jump and draws nothing across itself", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); palette({ { ID_A, RED } }, true); @@ -1891,6 +2168,7 @@ namespace tut for (const auto& [name, draws] : surfaces) { + INFO(name); idPass(draws); const std::vector ids = read(mIdMap); const std::vector px = edgePass(); @@ -1912,9 +2190,12 @@ namespace tut } } } - ensure(named(reverse, name + ": it covers pixels"), covered > 200); - ensure_equals(named(reverse, name + ": no jump marked"), marks, size_t(0)); - ensure_equals(named(reverse, name + ": nothing drawn across it"), drawn, size_t(0)); + UNSCOPED_INFO("it covers pixels"); + REQUIRE(covered > 200); + UNSCOPED_INFO("no jump marked"); + REQUIRE(marks == size_t(0)); + UNSCOPED_INFO("nothing drawn across it"); + REQUIRE(drawn == size_t(0)); } } } @@ -1922,20 +2203,21 @@ namespace tut // The wireframe's lines are as wide in the window wherever they are: along an edge receding from four to twelve // metres, on the same quad four times as far, and twice as wide at twice the width. The width across a line is // read as the sum of its coverage down a column, the line's width over the cosine of its slope. - template<> template<> - void alselectionoutline_object_t::test<24>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: the wireframe's lines are as wide in the window wherever they are", "[ALSelectionOutline]") { // The rows a column's runs of drawn pixels cover, bottom up. // The width down column x of the quad's diagonal, the middle of the three lines a column crosses. auto diagonal = [&](const std::vector& px, S32 x) { const std::vector> found = columnRuns(px, x); - ensure_equals("a column crosses the bottom edge, the diagonal and the top edge", found.size(), size_t(3)); + UNSCOPED_INFO("a column crosses the bottom edge, the diagonal and the top edge"); + REQUIRE(found.size() == size_t(3)); return ink(px, x, found[1].first, found[1].second); }; for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); palette({ { ID_A, RED } }, true); windowDepth(nullptr); @@ -1946,25 +2228,38 @@ namespace tut const F32 near_end = diagonal(near_px, 26); const F32 far_end = diagonal(near_px, 34); // The line's slope down the screen is about 0.91: a width of 1 reads as about 1.35 down a column. - ensure(named(reverse, "the width down the diagonal, near: " + std::to_string(near_end)), near_end > 1.15f && near_end < 1.55f); - ensure(named(reverse, "the same along the receding edge, far: " + std::to_string(far_end)), fabsf(far_end - near_end) < 0.1f); + { + INFO("the width down the diagonal, near"); + REQUIRE(near_end > 1.15f); + REQUIRE(near_end < 1.55f); + } + UNSCOPED_INFO("the same along the receding edge, far: " << far_end); + REQUIRE(fabsf(far_end - near_end) < 0.1f); const std::vector far_px = worldWireframe({ { quad(-4.f, 6.f, -4.f, 4.f, -12.f, -48.f), ID_A } }); - ensure(named(reverse, "the same four times as far"), fabsf(diagonal(far_px, 26) - near_end) < 0.1f && fabsf(diagonal(far_px, 34) - far_end) < 0.1f); + { + INFO("the same four times as far"); + REQUIRE(fabsf(diagonal(far_px, 26) - near_end) < 0.1f); + REQUIRE(fabsf(diagonal(far_px, 34) - far_end) < 0.1f); + } const std::vector wide_px = worldWireframe({ { quad(-1.f, 1.5f, -1.f, 1.f, -3.f, -12.f), ID_A } }, 2); const F32 narrow = diagonal(near_px, 30); const F32 wide = diagonal(wide_px, 30); - ensure(named(reverse, "twice as wide at twice the width: " + std::to_string(wide)), wide > 1.8f * narrow && wide < 2.2f * narrow); + { + INFO("twice as wide at twice the width"); + REQUIRE(wide > 1.8f * narrow); + REQUIRE(wide < 2.2f * narrow); + } } } // Across a line, coverage falls from its middle on either side, and is gone within a pixel and a half. - template<> template<> - void alselectionoutline_object_t::test<25>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a line's coverage falls from its middle to nothing on either side", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); palette({ { ID_A, RED } }, true); windowDepth(nullptr); @@ -1972,18 +2267,21 @@ namespace tut const std::vector px = wireframe({ Outline::WIRE_VISIBLE }, pixelProjection(), { { pixels(8, 56, 8, 56), ID_A } }); for (S32 y = 16; y < 48; ++y) { - const std::string row = ", row " + std::to_string(y); - ensure(named(reverse, "the line is drawn" + row), at(px, y, y).r > 127); + CAPTURE(y); + UNSCOPED_INFO("the line is drawn"); + REQUIRE(at(px, y, y).r > 127); for (S32 step : { -1, 1 }) { U32 last = at(px, y, y).r; for (S32 x = y + step; x != y + 4 * step; x += step) { const U32 r = at(px, x, y).r; - ensure(named(reverse, "coverage falls away from the line" + row), r <= last); + UNSCOPED_INFO("coverage falls away from the line"); + REQUIRE(r <= last); last = r; } - ensure_equals(named(reverse, "and is gone" + row), last, 0U); + UNSCOPED_INFO("and is gone"); + REQUIRE(last == 0U); } } } @@ -1991,11 +2289,11 @@ namespace tut // Lines behind something in the scene are drawn in the hidden colour with hidden selections shown, and not at // all without; the lines in front keep their colour. - template<> template<> - void alselectionoutline_object_t::test<26>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: lines behind the scene are dimmed or left out as hidden parts say", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); // The brightest of a row's pixels between two columns. @@ -2014,23 +2312,30 @@ namespace tut windowDepth(nullptr); std::vector px = worldWireframe({ { quadA(), ID_A } }); const U32 open = brightest(px, 25, 11, 18); - ensure(named(reverse, "the line drawn with nothing in front"), open > 127); + UNSCOPED_INFO("the line drawn with nothing in front"); + REQUIRE(open > 127); // Then with something three metres out over columns 0 to 19, in front of A's left part. occlude(3.f, 0, 0, 20, H); windowDepth(&mScene); px = worldWireframe({ { quadA(), ID_A } }); const U32 hidden = brightest(px, 25, 11, 18); - ensure(named(reverse, "the line in front drawn"), brightest(px, 38, 22, 30) > 127); - ensure(named(reverse, "the line behind dimmed: " + std::to_string(hidden) + " of " + std::to_string(open)), - hidden * 100 >= open * 35 && hidden * 100 <= open * 45); + UNSCOPED_INFO("the line in front drawn"); + REQUIRE(brightest(px, 38, 22, 30) > 127); + { + INFO("the line behind dimmed: " << hidden << " of " << open); + REQUIRE(hidden * 100 >= open * 35); + REQUIRE(hidden * 100 <= open * 45); + } Draw left_out = { quadA(), ID_A }; left_out.mShowHidden = false; palette({ { ID_A, RED } }, false); px = worldWireframe({ left_out }); - ensure_equals(named(reverse, "the line behind left out"), brightest(px, 25, 11, 18), 0U); - ensure(named(reverse, "the line in front kept"), brightest(px, 38, 22, 30) > 127); + UNSCOPED_INFO("the line behind left out"); + REQUIRE(brightest(px, 25, 11, 18) == 0U); + UNSCOPED_INFO("the line in front kept"); + REQUIRE(brightest(px, 38, 22, 30) > 127); } } @@ -2038,11 +2343,11 @@ namespace tut // point-sampled from half the resolution, as with RenderResolutionDivisor 2, nearer than the lines over half // their pixels on a surface turned away, and the polygon offset pulls them in front of it under either depth // convention. None is taken for hidden. - template<> template<> - void alselectionoutline_object_t::test<27>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: lines on their own surface in the scene's depth are all drawn", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); palette({ { ID_A, RED } }, true); // Turned about 60 degrees from the eye, five metres deep across its width. @@ -2066,20 +2371,22 @@ namespace tut lost += at(alone, x, y).r != at(on_surface, x, y).r ? 1 : 0; } } - ensure(named(reverse, "lines are drawn"), drawn > 100); - ensure_equals(named(reverse, "every line pixel drawn on its own surface as on nothing"), lost, size_t(0)); + UNSCOPED_INFO("lines are drawn"); + REQUIRE(drawn > 100); + UNSCOPED_INFO("every line pixel drawn on its own surface as on nothing"); + REQUIRE(lost == size_t(0)); } } // A face's cut-out texels draw no lines, its kept ones do; a rigged face's lines are where its joints put it; a // face the scene culls draws none. - template<> template<> - void alselectionoutline_object_t::test<28>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a face's lines follow its alpha cut and its pose and its culling", "[ALSelectionOutline]") { // Joint 0 moved 1.5 m along +x. const F32 right[12] = { 1.f, 0.f, 0.f, 1.5f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f }; for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); palette({ { ID_A, RED } }, true); windowDepth(nullptr); @@ -2089,38 +2396,48 @@ namespace tut const F32 mask = Outline::legacyAlphaCutoff(true, LLMaterial::DIFFUSE_ALPHA_MODE_MASK, 64, true); std::vector px = wireframe({ Outline::WIRE_VISIBLE }, pixelProjection(), { { pixels(0, W, 16, 48), ID_A, Outline::PRIORITY_ROOT, mask, ramp() } }); - ensure_equals(named(reverse, "no line across the cut-out texels"), ink(px, 8, 17, 24), 0.f); - ensure(named(reverse, "nor along their edge"), black(at(px, 8, 16))); - ensure(named(reverse, "lines across the kept ones"), ink(px, 40, 33, 40) > 0.5f && !black(at(px, 40, 16))); + UNSCOPED_INFO("no line across the cut-out texels"); + REQUIRE(ink(px, 8, 17, 24) == 0.f); + UNSCOPED_INFO("nor along their edge"); + REQUIRE(black(at(px, 8, 16))); + { + INFO("lines across the kept ones"); + REQUIRE(ink(px, 40, 33, 40) > 0.5f); + REQUIRE_FALSE(black(at(px, 40, 16))); + } // Posed, the quad covers columns 43 to 54 and rows 26 to 37. LLVertexBuffer* bound = quad(-0.5f, 0.5f, -0.5f, 0.5f, QUAD_Z, QUAD_Z, true); px = worldWireframe({ { bound, ID_A } }, 1, 1.f, right); - ensure(named(reverse, "the rigged face's lines where it is posed"), ink(px, 49, 26, 38) > 0.5f); - ensure(named(reverse, "not at its bind pose"), ink(px, 32, 26, 38) == 0.f); + UNSCOPED_INFO("the rigged face's lines where it is posed"); + REQUIRE(ink(px, 49, 26, 38) > 0.5f); + UNSCOPED_INFO("not at its bind pose"); + REQUIRE(ink(px, 32, 26, 38) == 0.f); // A face seen from behind, wound clockwise, as the id pass culls it: no lines unless double-sided. const LLVector3 back_on[4] = { LLVector3(16.f, 16.f, PIXEL_Z), LLVector3(16.f, 48.f, PIXEL_Z), LLVector3(48.f, 48.f, PIXEL_Z), LLVector3(48.f, 16.f, PIXEL_Z) }; Draw back = { quadAt(back_on), ID_A }; px = wireframe({ Outline::WIRE_VISIBLE }, pixelProjection(), { back }); - ensure_equals(named(reverse, "no lines on a face from behind"), lit(px), size_t(0)); + UNSCOPED_INFO("no lines on a face from behind"); + REQUIRE(lit(px) == size_t(0)); back.mDoubleSided = true; px = wireframe({ Outline::WIRE_VISIBLE }, pixelProjection(), { back }); - ensure(named(reverse, "lines on a double-sided one"), lit(px) > 50); + UNSCOPED_INFO("lines on a double-sided one"); + REQUIRE(lit(px) > 50); } } // On the HUD the window's depth also holds the scene's, wherever that is the nearer, so the HUD's lines are // tested against the id target's depth instead: drawn whatever the window's depth holds, here the nearest value, // and hidden where they lie behind another selected surface. - template<> template<> - void alselectionoutline_object_t::test<29>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: the HUD's lines test the id target's depth and not the window's", "[ALSelectionOutline]") { constexpr U32 REAR = 1; constexpr U32 FRONT = 2; for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); // The rear from 16 to 47 each way, its diagonal through x = y; the front nearer, over columns 28 to 43 // and rows 20 to 35, its diagonal through x = y + 8. The rear's diagonal passes behind the front at @@ -2139,7 +2456,7 @@ namespace tut for (bool show_hidden : { false, true }) { - const std::string hidden_parts = show_hidden ? ", hidden parts drawn" : ", hidden parts left out"; + CAPTURE(show_hidden); std::vector shown = draws; for (Draw& draw : shown) { @@ -2147,18 +2464,31 @@ namespace tut } palette({ { REAR, RED }, { FRONT, GREEN } }, show_hidden); const std::vector px = wireframe({ Outline::WIRE_ID_DEPTH }, pixelProjection(), shown); - ensure(named(reverse, "the rear's line in the open" + hidden_parts), red(at(px, 20, 20)) && at(px, 20, 20).r > 200); - ensure(named(reverse, "the front's line" + hidden_parts), green(at(px, 40, 32)) && at(px, 40, 32).g > 200); + { + INFO("the rear's line in the open"); + REQUIRE(red(at(px, 20, 20))); + REQUIRE(at(px, 20, 20).r > 200); + } + { + INFO("the front's line"); + REQUIRE(green(at(px, 40, 32))); + REQUIRE(at(px, 40, 32).g > 200); + } if (show_hidden) { const U32 dimmed = at(px, 32, 32).r; const U32 open = at(px, 20, 20).r; - ensure(named(reverse, "the rear's line behind the front dimmed: " + std::to_string(dimmed)), - red(at(px, 32, 32)) && dimmed * 100 >= open * 35 && dimmed * 100 <= open * 45); + { + INFO("the rear's line behind the front dimmed: " << dimmed << " of " << open); + REQUIRE(red(at(px, 32, 32))); + REQUIRE(dimmed * 100 >= open * 35); + REQUIRE(dimmed * 100 <= open * 45); + } } else { - ensure(named(reverse, "the rear's line behind the front left out"), black(at(px, 32, 32))); + UNSCOPED_INFO("the rear's line behind the front left out"); + REQUIRE(black(at(px, 32, 32))); } } } @@ -2166,29 +2496,33 @@ namespace tut // The wireframe's width, a pixel at a UI scale of 1 and scaled with it, the same for the world and the HUD; and // the polygon offset its lines need over a resampled depth. - template<> template<> - void alselectionoutline_object_t::test<30>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: the wireframe is a pixel wide at UI scale 1 and scales with it", "[ALSelectionOutline]") { - ensure_equals("a pixel at 1", Outline::lineWidth(Outline::WIRE_WIDTH, 1.f), 1); - ensure_equals("two at 2", Outline::lineWidth(Outline::WIRE_WIDTH, 2.f), 2); + CHECK(Outline::lineWidth(Outline::WIRE_WIDTH, 1.f) == 1); + CHECK(Outline::lineWidth(Outline::WIRE_WIDTH, 2.f) == 2); for (bool hud : { false, true }) { + CAPTURE(hud); const Outline::View scaled = Outline::makeView(identity(), identity(), 2.f, 2.f, hud); - ensure_equals("its width at the UI scale", scaled.mWireWidth, 2); - } - ensure_equals("a window depth at the scene's resolution", Outline::wireOffsetFactor(1.f), 2.f); - ensure_equals("at half of it", Outline::wireOffsetFactor(2.f), 3.f); - ensure_equals("never under the window's", Outline::wireOffsetFactor(0.5f), 2.f); + UNSCOPED_INFO("its width at the UI scale"); + REQUIRE(scaled.mWireWidth == 2); + } + UNSCOPED_INFO("a window depth at the scene's resolution"); + CHECK(Outline::wireOffsetFactor(1.f) == 2.f); + UNSCOPED_INFO("at half of it"); + CHECK(Outline::wireOffsetFactor(2.f) == 3.f); + UNSCOPED_INFO("never under the window's"); + CHECK(Outline::wireOffsetFactor(0.5f) == 2.f); } // A triangle reaching behind the eye, as a floor or a wall a builder stands at has, is clipped at the near plane // where GL clips it: its edges' parts in front are drawn at their width, the cut along the near plane draws no // line, and its texture coordinates are carried to the cut, so the alpha cut still follows the texture. - template<> template<> - void alselectionoutline_object_t::test<31>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a triangle reaching behind the eye is wireframed as GL clips it", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); palette({ { ID_A, RED } }, true); windowDepth(nullptr); @@ -2201,12 +2535,21 @@ namespace tut LLVertexBuffer* reaching = quadAt(one_behind); std::vector px = worldWireframe({ { reaching, ID_A } }); std::vector> found = columnRuns(px, 40); - ensure_equals(named(reverse, "column 40 crosses the bottom edge, the diagonal and the top edge"), found.size(), size_t(3)); + UNSCOPED_INFO("column 40 crosses the bottom edge, the diagonal and the top edge"); + REQUIRE(found.size() == size_t(3)); // The diagonal's slope down the screen is about a third: a width of 1 reads as about 1.05 down a column. const F32 across = ink(px, 40, found[1].first, found[1].second); - ensure(named(reverse, "the diagonal at its width: " + std::to_string(across)), across > 0.9f && across < 1.25f); + { + INFO("the diagonal at its width"); + REQUIRE(across > 0.9f); + REQUIRE(across < 1.25f); + } // The top edge, along row 41.8, has one triangle, which draws the half of its line inside the quad. - ensure(named(reverse, "the top edge drawn inside the quad"), at(px, 40, 41).r > 127 && black(at(px, 40, 42))); + { + INFO("the top edge drawn inside the quad"); + REQUIRE(at(px, 40, 41).r > 127); + REQUIRE(black(at(px, 40, 42))); + } // Two corners behind the eye: a wall crossing the near plane at the middle of the view, column 32, and // reaching ten metres out at column 43. Its edges all run near column 43; the cut at column 32 is no edge. @@ -2224,15 +2567,19 @@ namespace tut at_edges += black(at(px, x, y)) ? 0 : 1; } } - ensure_equals(named(reverse, "no line along the near plane's cut"), at_cut, size_t(0)); - ensure(named(reverse, "the edges in front drawn: " + std::to_string(at_edges)), at_edges > 40); + UNSCOPED_INFO("no line along the near plane's cut"); + REQUIRE(at_cut == size_t(0)); + UNSCOPED_INFO("the edges in front drawn"); + REQUIRE(at_edges > 40); // The first quad textured with the ramp, masked at half: its diagonal's texture coordinate runs from 0 // behind the eye to 1, about 0.43 at column 20 and 0.57 at column 40. const F32 mask = Outline::legacyAlphaCutoff(true, LLMaterial::DIFFUSE_ALPHA_MODE_MASK, 128, true); px = worldWireframe({ { reaching, ID_A, Outline::PRIORITY_ROOT, mask, ramp() } }); - ensure_equals(named(reverse, "no line where the clipped triangle's texture is cut"), ink(px, 20, 24, 32), 0.f); - ensure(named(reverse, "a line where it is kept"), ink(px, 40, 32, 38) > 0.5f); + UNSCOPED_INFO("no line where the clipped triangle's texture is cut"); + REQUIRE(ink(px, 20, 24, 32) == 0.f); + UNSCOPED_INFO("a line where it is kept"); + REQUIRE(ink(px, 40, 32, 38) > 0.5f); } } @@ -2240,109 +2587,165 @@ namespace tut // objects ask for: the outline and the glow the id pass and the passes after it, the world's wireframe none of // them, since it is tested against the window's depth, and the HUD's the id pass alone, whose depth it is tested // against. What glows asks for nothing else, and a selection's role keeps its object whatever glows. - template<> template<> - void alselectionoutline_object_t::test<32>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: the outline and wireframe and glow each run only the passes they need", "[ALSelectionOutline]") { for (bool hud : { false, true }) { - const std::string where = hud ? " on the HUD" : " in the world"; + CAPTURE(hud); Outline::Wants wants; wants.mContour = true; Outline::Passes passes = Outline::passesFor(wants, hud, true); - ensure("the outline alone runs the id pass" + where, passes.mIds); - ensure("and the edge passes" + where, passes.mEdges); - ensure("and no wireframe" + where, !passes.mWireframe); + UNSCOPED_INFO("the outline alone runs the id pass"); + REQUIRE(passes.mIds); + UNSCOPED_INFO("and the edge passes"); + REQUIRE(passes.mEdges); + UNSCOPED_INFO("and no wireframe"); + REQUIRE_FALSE(passes.mWireframe); wants.mWireframe = true; passes = Outline::passesFor(wants, hud, true); - ensure("both run everything" + where, passes.mIds && passes.mEdges && passes.mWireframe); + { + INFO("both run everything"); + REQUIRE(passes.mIds); + REQUIRE(passes.mEdges); + REQUIRE(passes.mWireframe); + } passes = Outline::passesFor(wants, hud, false); - ensure("a wireframe program that did not load leaves the outline" + where, - passes.mIds && passes.mEdges && !passes.mWireframe); + { + INFO("a wireframe program that did not load leaves the outline"); + REQUIRE(passes.mIds); + REQUIRE(passes.mEdges); + REQUIRE_FALSE(passes.mWireframe); + } wants.mContour = false; passes = Outline::passesFor(wants, hud, true); - ensure("the wireframe alone is drawn" + where, passes.mWireframe); - ensure("without the edge passes" + where, !passes.mEdges); - ensure_equals("with the id pass only on the HUD" + where, passes.mIds, hud); + UNSCOPED_INFO("the wireframe alone is drawn"); + REQUIRE(passes.mWireframe); + UNSCOPED_INFO("without the edge passes"); + REQUIRE_FALSE(passes.mEdges); + UNSCOPED_INFO("with the id pass only on the HUD"); + REQUIRE(passes.mIds == hud); passes = Outline::passesFor(wants, hud, false); - ensure("nothing when its program did not load" + where, !passes.any()); + UNSCOPED_INFO("nothing when its program did not load"); + REQUIRE_FALSE(passes.any()); wants.mGlow = true; passes = Outline::passesFor(wants, hud, true); - ensure("a glow beside a wireframe runs everything" + where, passes.mIds && passes.mEdges && passes.mWireframe); + { + INFO("a glow beside a wireframe runs everything"); + REQUIRE(passes.mIds); + REQUIRE(passes.mEdges); + REQUIRE(passes.mWireframe); + } wants.mWireframe = false; passes = Outline::passesFor(wants, hud, true); - ensure("a glow with the outline and the wireframe off runs the id and edge passes" + where, - passes.mIds && passes.mEdges && !passes.mWireframe); - ensure("and whether or not the wireframe loaded" + where, Outline::passesFor(wants, hud, false).mEdges); + { + INFO("a glow with the outline and the wireframe off runs the id and edge passes"); + REQUIRE(passes.mIds); + REQUIRE(passes.mEdges); + REQUIRE_FALSE(passes.mWireframe); + } + UNSCOPED_INFO("and whether or not the wireframe loaded"); + REQUIRE(Outline::passesFor(wants, hud, false).mEdges); wants.mGlow = false; - ensure("nothing with all three off" + where, !Outline::passesFor(wants, hud, true).any()); + UNSCOPED_INFO("nothing with all three off"); + REQUIRE_FALSE(Outline::passesFor(wants, hud, true).any()); } const U32 both = Outline::PART_CONTOUR | Outline::PART_WIREFRAME; - ensure_equals("what glows has no contour and no wireframe", Outline::partsFor(Outline::PRIORITY_HOVER, both), 0U); - ensure_equals("a selection keeps what it asks for", Outline::partsFor(Outline::PRIORITY_ROOT, both), both); - - ensure("what glows is in the id pass", Outline::inIdPass(0, Outline::PRIORITY_HOVER, false)); - ensure("and drawn there", Outline::outlined(0, Outline::PRIORITY_HOVER)); - ensure("a contour is", Outline::inIdPass(Outline::PART_CONTOUR, Outline::PRIORITY_ROOT, false)); - ensure("a world wireframe alone is not", !Outline::inIdPass(Outline::PART_WIREFRAME, Outline::PRIORITY_ROOT, false)); - ensure("a HUD wireframe alone is, for its depth", Outline::inIdPass(Outline::PART_WIREFRAME, Outline::PRIORITY_ROOT, true)); - ensure("and drawn nowhere", !Outline::outlined(Outline::PART_WIREFRAME, Outline::PRIORITY_ROOT)); + UNSCOPED_INFO("what glows has no contour and no wireframe"); + CHECK(Outline::partsFor(Outline::PRIORITY_HOVER, both) == 0U); + UNSCOPED_INFO("a selection keeps what it asks for"); + CHECK(Outline::partsFor(Outline::PRIORITY_ROOT, both) == both); + + UNSCOPED_INFO("what glows is in the id pass"); + CHECK(Outline::inIdPass(0, Outline::PRIORITY_HOVER, false)); + UNSCOPED_INFO("and drawn there"); + CHECK(Outline::outlined(0, Outline::PRIORITY_HOVER)); + UNSCOPED_INFO("a contour is"); + CHECK(Outline::inIdPass(Outline::PART_CONTOUR, Outline::PRIORITY_ROOT, false)); + UNSCOPED_INFO("a world wireframe alone is not"); + CHECK_FALSE(Outline::inIdPass(Outline::PART_WIREFRAME, Outline::PRIORITY_ROOT, false)); + UNSCOPED_INFO("a HUD wireframe alone is, for its depth"); + CHECK(Outline::inIdPass(Outline::PART_WIREFRAME, Outline::PRIORITY_ROOT, true)); + UNSCOPED_INFO("and drawn nowhere"); + CHECK_FALSE(Outline::outlined(Outline::PART_WIREFRAME, Outline::PRIORITY_ROOT)); for (Outline::EWirePass pass : { Outline::WIRE_VISIBLE, Outline::WIRE_HIDDEN, Outline::WIRE_ID_DEPTH }) { - ensure("what glows has no wireframe in any pass", - !Outline::inWirePass(Outline::partsFor(Outline::PRIORITY_HOVER, both), true, pass)); - ensure("nor an outline alone", !Outline::inWirePass(Outline::PART_CONTOUR, true, pass)); + CAPTURE(pass); + UNSCOPED_INFO("what glows has no wireframe in any pass"); + REQUIRE_FALSE(Outline::inWirePass(Outline::partsFor(Outline::PRIORITY_HOVER, both), true, pass)); + UNSCOPED_INFO("nor an outline alone"); + REQUIRE_FALSE(Outline::inWirePass(Outline::PART_CONTOUR, true, pass)); + } + UNSCOPED_INFO("a wireframe's visible lines"); + CHECK(Outline::inWirePass(Outline::PART_WIREFRAME, false, Outline::WIRE_VISIBLE)); + { + INFO("its hidden ones only where hidden parts are drawn"); + CHECK_FALSE(Outline::inWirePass(Outline::PART_WIREFRAME, false, Outline::WIRE_HIDDEN)); + CHECK(Outline::inWirePass(Outline::PART_WIREFRAME, true, Outline::WIRE_HIDDEN)); } - ensure("a wireframe's visible lines", Outline::inWirePass(Outline::PART_WIREFRAME, false, Outline::WIRE_VISIBLE)); - ensure("its hidden ones only where hidden parts are drawn", - !Outline::inWirePass(Outline::PART_WIREFRAME, false, Outline::WIRE_HIDDEN) && - Outline::inWirePass(Outline::PART_WIREFRAME, true, Outline::WIRE_HIDDEN)); - ensure("a glow never replaces a selection", !Outline::replaces(Outline::PRIORITY_CONTEXT, Outline::PRIORITY_HOVER)); - ensure("a selection replaces a glow", Outline::replaces(Outline::PRIORITY_HOVER, Outline::PRIORITY_CHILD)); - ensure("what a drag takes out replaces a root", Outline::replaces(Outline::PRIORITY_ROOT, Outline::PRIORITY_SUBTRACT)); - ensure("at one priority the later", Outline::replaces(Outline::PRIORITY_ROOT, Outline::PRIORITY_ROOT)); + UNSCOPED_INFO("a glow never replaces a selection"); + CHECK_FALSE(Outline::replaces(Outline::PRIORITY_CONTEXT, Outline::PRIORITY_HOVER)); + UNSCOPED_INFO("a selection replaces a glow"); + CHECK(Outline::replaces(Outline::PRIORITY_HOVER, Outline::PRIORITY_CHILD)); + UNSCOPED_INFO("what a drag takes out replaces a root"); + CHECK(Outline::replaces(Outline::PRIORITY_ROOT, Outline::PRIORITY_SUBTRACT)); + UNSCOPED_INFO("at one priority the later"); + CHECK(Outline::replaces(Outline::PRIORITY_ROOT, Outline::PRIORITY_ROOT)); } // The glow's reach and profile, the fades of what the pointer is over, and how long the targets are kept: each // object fades in over the fade time while it is named, the one before it fading out as it does, and is dropped // at nothing or when its object is gone. - template<> template<> - void alselectionoutline_object_t::test<33>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: the glow's reach and falloff and the hover fades and target lifetimes", "[ALSelectionOutline]") { const F32 nan = std::numeric_limits::quiet_NaN(); - ensure_equals("9 pixels at the default thickness", Outline::glowRadius(Outline::DEFAULT_GLOW_THICKNESS, 1.f), 9); - ensure_equals("scaled with the UI", Outline::glowRadius(Outline::DEFAULT_GLOW_THICKNESS, 2.f), 18); - ensure_equals("held to the widest", Outline::glowRadius(100.f, 1.f), (S32)Outline::MAX_GLOW_RADIUS); - ensure_equals("a pixel at least", Outline::glowRadius(-1.f, 1.f), 1); - ensure_equals("the default where the setting is not a number", Outline::glowRadius(nan, 1.f), 9); + CHECK(Outline::glowRadius(Outline::DEFAULT_GLOW_THICKNESS, 1.f) == 9); + UNSCOPED_INFO("scaled with the UI"); + CHECK(Outline::glowRadius(Outline::DEFAULT_GLOW_THICKNESS, 2.f) == 18); + UNSCOPED_INFO("held to the widest"); + CHECK(Outline::glowRadius(100.f, 1.f) == (S32)Outline::MAX_GLOW_RADIUS); + UNSCOPED_INFO("a pixel at least"); + CHECK(Outline::glowRadius(-1.f, 1.f) == 1); + UNSCOPED_INFO("the default where the setting is not a number"); + CHECK(Outline::glowRadius(nan, 1.f) == 9); const Outline::View defaults = Outline::makeView(identity(), identity(), 2.f, 1.f, false); - ensure_equals("a view's glow by default", defaults.mGlowRadius, 9); - ensure_equals("and its brightness", defaults.mGlowBrightness, Outline::DEFAULT_GLOW_BRIGHTNESS); - ensure_equals("a brightness that is not a number is the default", - Outline::makeView(identity(), identity(), 2.f, 1.f, false, 0.6f, nan).mGlowBrightness, - Outline::DEFAULT_GLOW_BRIGHTNESS); - ensure_equals("and one past the brightest held to it", - Outline::makeView(identity(), identity(), 2.f, 1.f, false, 0.6f, 1000.f).mGlowBrightness, - Outline::MAX_GLOW_BRIGHTNESS); + UNSCOPED_INFO("a view's glow by default"); + CHECK(defaults.mGlowRadius == 9); + UNSCOPED_INFO("and its brightness"); + CHECK(defaults.mGlowBrightness == Outline::DEFAULT_GLOW_BRIGHTNESS); + UNSCOPED_INFO("a brightness that is not a number is the default"); + CHECK(Outline::makeView(identity(), identity(), 2.f, 1.f, false, 0.6f, nan).mGlowBrightness == + Outline::DEFAULT_GLOW_BRIGHTNESS); + UNSCOPED_INFO("and one past the brightest held to it"); + CHECK(Outline::makeView(identity(), identity(), 2.f, 1.f, false, 0.6f, 1000.f).mGlowBrightness == + Outline::MAX_GLOW_BRIGHTNESS); // The silhouette is half a texel out from its texel's centre. - ensure_approximately_equals_range("opaque at the silhouette at the default brightness", - Outline::glowOpacity(0.5f, 9, 4.f), 1.f, 1e-6f); - ensure_approximately_equals_range("half as opaque at half of it", Outline::glowOpacity(0.5f, 9, 2.f), 0.5f, 1e-6f); - ensure_approximately_equals_range("half way out, half of the silhouette's", Outline::glowOpacity(5.f, 9, 4.f), 0.5f, - 1e-6f); - ensure_equals("nothing at its reach", Outline::glowOpacity(9.5f, 9, 4.f), 0.f); - ensure_equals("nor past it", Outline::glowOpacity(30.f, 9, 4.f), 0.f); - ensure_equals("held to opaque", Outline::glowOpacity(2.f, 9, Outline::MAX_GLOW_BRIGHTNESS), 1.f); + UNSCOPED_INFO("opaque at the silhouette at the default brightness"); + CHECK_THAT(Outline::glowOpacity(0.5f, 9, 4.f), Catch::Matchers::WithinAbs(1.f, 1e-6f)); + UNSCOPED_INFO("half as opaque at half of it"); + CHECK_THAT(Outline::glowOpacity(0.5f, 9, 2.f), Catch::Matchers::WithinAbs(0.5f, 1e-6f)); + UNSCOPED_INFO("half way out, half of the silhouette's"); + CHECK_THAT(Outline::glowOpacity(5.f, 9, 4.f), Catch::Matchers::WithinAbs(0.5f, 1e-6f)); + UNSCOPED_INFO("nothing at its reach"); + CHECK(Outline::glowOpacity(9.5f, 9, 4.f) == 0.f); + UNSCOPED_INFO("nor past it"); + CHECK(Outline::glowOpacity(30.f, 9, 4.f) == 0.f); + UNSCOPED_INFO("held to opaque"); + CHECK(Outline::glowOpacity(2.f, 9, Outline::MAX_GLOW_BRIGHTNESS) == 1.f); const F32 last_step = Outline::glowOpacity(9.25f, 9, 4.f); - ensure("fading smoothly into nothing: " + std::to_string(last_step), last_step > 0.f && last_step < 0.01f); + { + INFO("fading smoothly into nothing"); + CHECK(last_step > 0.f); + CHECK(last_step < 0.01f); + } F32 previous = 2.f; bool falling = true; for (S32 step = 0; step <= 40; ++step) @@ -2351,16 +2754,23 @@ namespace tut falling = falling && opacity <= previous; previous = opacity; } - ensure("falling all the way out", falling); - - ensure_approximately_equals_range("half way in half the fade time", ALHoverGlow::stepFade(0.f, 0.05f, 0.1f, true), - 0.5f, 1e-6f); - ensure_approximately_equals_range("out as fast", ALHoverGlow::stepFade(0.5f, 0.05f, 0.1f, false), 0.f, 1e-6f); - ensure_equals("held to one", ALHoverGlow::stepFade(0.9f, 1.f, 0.1f, true), 1.f); - ensure_equals("and to nothing", ALHoverGlow::stepFade(0.1f, 1.f, 0.1f, false), 0.f); - ensure_equals("at once with no fade time", ALHoverGlow::stepFade(0.f, 0.f, 0.f, true), 1.f); - ensure_equals("or one that is not a number", ALHoverGlow::stepFade(1.f, 0.f, nan, false), 0.f); - ensure_equals("time running back moves nothing", ALHoverGlow::stepFade(0.5f, -1.f, 0.1f, true), 0.5f); + UNSCOPED_INFO("falling all the way out"); + CHECK(falling); + + UNSCOPED_INFO("half way in half the fade time"); + CHECK_THAT(ALHoverGlow::stepFade(0.f, 0.05f, 0.1f, true), Catch::Matchers::WithinAbs(0.5f, 1e-6f)); + UNSCOPED_INFO("out as fast"); + CHECK_THAT(ALHoverGlow::stepFade(0.5f, 0.05f, 0.1f, false), Catch::Matchers::WithinAbs(0.f, 1e-6f)); + UNSCOPED_INFO("held to one"); + CHECK(ALHoverGlow::stepFade(0.9f, 1.f, 0.1f, true) == 1.f); + UNSCOPED_INFO("and to nothing"); + CHECK(ALHoverGlow::stepFade(0.1f, 1.f, 0.1f, false) == 0.f); + UNSCOPED_INFO("at once with no fade time"); + CHECK(ALHoverGlow::stepFade(0.f, 0.f, 0.f, true) == 1.f); + UNSCOPED_INFO("or one that is not a number"); + CHECK(ALHoverGlow::stepFade(1.f, 0.f, nan, false) == 0.f); + UNSCOPED_INFO("time running back moves nothing"); + CHECK(ALHoverGlow::stepFade(0.5f, -1.f, 0.1f, true) == 0.5f); // Steps of an eighth of a second over a quarter: halves, exactly. const LLUUID a("c0ffee00-0000-4000-8000-00000000000a"); @@ -2387,8 +2797,11 @@ namespace tut }; // A frame an eighth of a second long, the settle time between one and two of them. const F32 fade_time = 0.25f; - ensure("the settle time lies between one frame and two", - ALHoverGlow::SETTLE_SECONDS > 0.125 && ALHoverGlow::SETTLE_SECONDS <= 0.25); + { + INFO("the settle time lies between one frame and two"); + CHECK(ALHoverGlow::SETTLE_SECONDS > 0.125); + CHECK(ALHoverGlow::SETTLE_SECONDS <= 0.25); + } U32 frame = 0; auto at = [&](const LLUUID& id, F64 now) { @@ -2398,117 +2811,166 @@ namespace tut }; at(a, 10.0); - ensure("named, nothing glows yet", glow.glows().empty()); + UNSCOPED_INFO("named, nothing glows yet"); + REQUIRE(glow.glows().empty()); at(a, 10.125); - ensure("nor before the name has settled", glow.glows().empty()); + UNSCOPED_INFO("nor before the name has settled"); + REQUIRE(glow.glows().empty()); at(a, 10.25); - ensure_equals("settled, it fades in", fade(a), 0.5f); + UNSCOPED_INFO("settled, it fades in"); + CHECK(fade(a) == 0.5f); glow.update(10.25, frame, fade_time, is_alive); - ensure_equals("a second update in a frame moves nothing", fade(a), 0.5f); + UNSCOPED_INFO("a second update in a frame moves nothing"); + CHECK(fade(a) == 0.5f); at(a, 10.375); - ensure_equals("in over the fade time", fade(a), 1.f); + UNSCOPED_INFO("in over the fade time"); + CHECK(fade(a) == 1.f); at(b, 10.5); at(b, 10.625); - ensure_equals("the glow stays while the next name settles", fade(a), 1.f); - ensure_equals("which does not glow yet", fade(b), -1.f); + UNSCOPED_INFO("the glow stays while the next name settles"); + CHECK(fade(a) == 1.f); + UNSCOPED_INFO("which does not glow yet"); + CHECK(fade(b) == -1.f); at(b, 10.75); - ensure_equals("then the one before fades out", fade(a), 0.5f); - ensure_equals("while the next fades in", fade(b), 0.5f); + UNSCOPED_INFO("then the one before fades out"); + CHECK(fade(a) == 0.5f); + UNSCOPED_INFO("while the next fades in"); + CHECK(fade(b) == 0.5f); at(b, 10.875); - ensure_equals("dropped at nothing", fade(a), -1.f); - ensure_equals("one glows", glow.glows().size(), size_t(1)); + UNSCOPED_INFO("dropped at nothing"); + CHECK(fade(a) == -1.f); + UNSCOPED_INFO("one glows"); + REQUIRE(glow.glows().size() == size_t(1)); at(LLUUID::null, 11.0); at(b, 11.125); - ensure_equals("a frame off it, at its edge or a seam, does not put it out", fade(b), 1.f); + UNSCOPED_INFO("a frame off it, at its edge or a seam, does not put it out"); + CHECK(fade(b) == 1.f); at(LLUUID::null, 11.25); at(LLUUID::null, 11.375); - ensure_equals("nor does leaving it, until that settles", fade(b), 1.f); + UNSCOPED_INFO("nor does leaving it, until that settles"); + CHECK(fade(b) == 1.f); at(LLUUID::null, 11.5); - ensure_equals("then it fades out", fade(b), 0.5f); + UNSCOPED_INFO("then it fades out"); + CHECK(fade(b) == 0.5f); at(a, 11.625); at(b, 11.75); at(a, 11.875); at(LLUUID::null, 12.0); - ensure("a sweep across objects lights none of them", glow.glows().empty()); + UNSCOPED_INFO("a sweep across objects lights none of them"); + REQUIRE(glow.glows().empty()); at(a, 12.125); at(a, 12.25); at(a, 12.375); - ensure_equals("one rested on lights", fade(a), 0.5f); + UNSCOPED_INFO("one rested on lights"); + CHECK(fade(a) == 0.5f); alive.clear(); at(a, 12.5); - ensure("dropped when its object is gone, named or not", glow.glows().empty()); + UNSCOPED_INFO("dropped when its object is gone, named or not"); + REQUIRE(glow.glows().empty()); at(LLUUID::null, 12.625); at(LLUUID::null, 12.75); - ensure("nothing named, nothing glows", glow.glows().empty()); + UNSCOPED_INFO("nothing named, nothing glows"); + REQUIRE(glow.glows().empty()); glow.hover(b, 100); - ensure("named a frame ago, it is still named", glow.hovered(101) == b); - ensure("unnamed for longer, the name lapses", glow.hovered(102).isNull()); + UNSCOPED_INFO("named a frame ago, it is still named"); + CHECK(glow.hovered(101) == b); + UNSCOPED_INFO("unnamed for longer, the name lapses"); + CHECK(glow.hovered(102).isNull()); const F64 kept = Outline::TARGET_KEEP_SECONDS; - ensure("a target drawn with this frame is kept", !Outline::stale(5, 100.0, 5, 100.0 + kept * 2.0)); - ensure("and the next, however long it took", !Outline::stale(5, 100.0, 6, 100.0 + kept * 2.0)); - ensure("and for its time, however many frames pass", !Outline::stale(5, 100.0, 500, 100.0 + kept * 0.5)); - ensure("given up after both", Outline::stale(5, 100.0, 7, 100.0 + kept)); - ensure("across the frame counter's wrap", - !Outline::stale(0xFFFFFFFFu, 100.0, 0u, 200.0) && Outline::stale(0xFFFFFFFEu, 100.0, 1u, 200.0)); + UNSCOPED_INFO("a target drawn with this frame is kept"); + CHECK_FALSE(Outline::stale(5, 100.0, 5, 100.0 + kept * 2.0)); + UNSCOPED_INFO("and the next, however long it took"); + CHECK_FALSE(Outline::stale(5, 100.0, 6, 100.0 + kept * 2.0)); + UNSCOPED_INFO("and for its time, however many frames pass"); + CHECK_FALSE(Outline::stale(5, 100.0, 500, 100.0 + kept * 0.5)); + UNSCOPED_INFO("given up after both"); + CHECK(Outline::stale(5, 100.0, 7, 100.0 + kept)); + { + INFO("across the frame counter's wrap"); + CHECK_FALSE(Outline::stale(0xFFFFFFFFu, 100.0, 0u, 200.0)); + CHECK(Outline::stale(0xFFFFFFFEu, 100.0, 1u, 200.0)); + } } // A glowing object draws a halo around its silhouette in its colour, falling off from the silhouette as // glowOpacity says, as bright as asked, to nothing at its reach; and nothing over itself, its own folds included, // whose jumps the jump pass leaves unmarked. - template<> template<> - void alselectionoutline_object_t::test<34>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a glowing object draws a halo around its silhouette in its colour", "[ALSelectionOutline]") { constexpr S32 RADIUS = 6; for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); // Columns 20 to 39 and rows 20 to 43, and a fold of the same object nearer over its middle, columns 25 to // 34: selected, the near side of a jump in its own surface. pixelIds({ { pixels(20, 40, 20, 44), ID_A }, { quad(25.f, 35.f, 25.f, 39.f, -1.f, -1.f), ID_A } }); markPasses(glowView(RADIUS)); - ensure(named(reverse, "selected, the fold's edge is the near side of a jump"), markedAt(25, 32) && markedAt(34, 32)); + { + INFO("selected, the fold's edge is the near side of a jump"); + REQUIRE(markedAt(25, 32)); + REQUIRE(markedAt(34, 32)); + } pixelIds({ hoverDraw(pixels(20, 40, 20, 44), ID_A), hoverDraw(quad(25.f, 35.f, 25.f, 39.f, -1.f, -1.f), ID_A) }); palette({ { ID_A, GREEN } }, false); for (F32 brightness : { Outline::DEFAULT_GLOW_BRIGHTNESS, 2.f }) { - const std::string at_brightness = " at brightness " + std::to_string(brightness); + CAPTURE(brightness); const std::vector px = edgePass(glowView(RADIUS, brightness), nullptr, false, nullptr, true); - ensure(named(reverse, "glowing, its fold's edge is not marked"), !markedAt(25, 32) && !markedAt(34, 32)); + { + INFO("glowing, its fold's edge is not marked"); + REQUIRE_FALSE(markedAt(25, 32)); + REQUIRE_FALSE(markedAt(34, 32)); + } for (S32 k = 0; k <= RADIUS + 2; ++k) { // Column 40 + k is k + 1 from the centre of the nearest texel, in column 39. const F32 expected = 255.f * Outline::glowOpacity((F32)(k + 1), RADIUS, brightness); const Pixel p = at(px, 40 + k, 32); - ensure(named(reverse, "the glow " + std::to_string(k + 1) + " pixels out" + at_brightness + ": " + - std::to_string(p.g) + " for " + std::to_string(expected)), - std::fabs((F32)p.g - expected) <= 2.f && p.r == 0 && p.b == 0); + { + INFO("the glow " << k + 1 << " pixels out: " << (U32)p.g << " for " << expected); + REQUIRE(std::fabs((F32)p.g - expected) <= 2.f); + REQUIRE(p.r == 0); + REQUIRE(p.b == 0); + } } const U8 between = at(px, 43, 32).g; - ensure(named(reverse, "soft, not a solid band" + at_brightness), between > 10 && between < 245); - ensure(named(reverse, "nothing over the object" + at_brightness), - black(at(px, 30, 32)) && black(at(px, 39, 32)) && black(at(px, 20, 20))); - ensure(named(reverse, "nor over its far side beside its fold" + at_brightness), - black(at(px, 24, 32)) && black(at(px, 35, 32))); + { + INFO("soft, not a solid band"); + REQUIRE(between > 10); + REQUIRE(between < 245); + } + { + INFO("nothing over the object"); + REQUIRE(black(at(px, 30, 32))); + REQUIRE(black(at(px, 39, 32))); + REQUIRE(black(at(px, 20, 20))); + } + { + INFO("nor over its far side beside its fold"); + REQUIRE(black(at(px, 24, 32))); + REQUIRE(black(at(px, 35, 32))); + } } } } // A selection's contour and the edges between selected objects are laid over the glow; and beside a glowing // object a selection is outlined as though it were not there, except where it stands in front. - template<> template<> - void alselectionoutline_object_t::test<35>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a selection's contour and edges are laid over the glow", "[ALSelectionOutline]") { constexpr U32 ID_GLOW = 400; for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); palette({ { ID_A, RED }, { ID_B, RED }, { ID_GLOW, GREEN } }, true); @@ -2517,11 +2979,17 @@ namespace tut std::vector px = edgePass(glowView(9), nullptr, false, nullptr, true); for (S32 x : { 38, 39 }) { + CAPTURE(x); const Pixel p = at(px, x, 32); - ensure(named(reverse, "the contour opaque over the glow at column " + std::to_string(x)), - p.r == 255 && p.g == 0); + INFO("the contour opaque over the glow"); + REQUIRE(p.r == 255); + REQUIRE(p.g == 0); + } + { + INFO("the glow past the contour"); + REQUIRE(green(at(px, 37, 32))); + REQUIRE(green(at(px, 36, 32))); } - ensure(named(reverse, "the glow past the contour"), green(at(px, 37, 32)) && green(at(px, 36, 32))); // Touching it, at column 40: the selection's contour drawn over the glowing object as over nothing, and // no edge between them. @@ -2531,10 +2999,14 @@ namespace tut px = edgePass(glowView(9), nullptr, false, nullptr, true); for (S32 x : { 37, 38, 39, 40, 41 }) { + CAPTURE(x); const Pixel p = at(px, x, 32); const Pixel q = at(alone, x, 32); - ensure(named(reverse, "the contour beside it as without it at column " + std::to_string(x)), - p.r == q.r && p.g == q.g && p.b == q.b && p.a == q.a); + INFO("the contour beside it as without it"); + REQUIRE(p.r == q.r); + REQUIRE(p.g == q.g); + REQUIRE(p.b == q.b); + REQUIRE(p.a == q.a); } // Two selected objects touching at column 30, their edge on A's side, and a glowing one nearer below @@ -2545,41 +3017,53 @@ namespace tut // Pixel (29, 20) is a pixel from B and three from the glowing object's top row. const F32 glow = Outline::glowOpacity(3.f, 9, Outline::DEFAULT_GLOW_BRIGHTNESS); const Pixel edge = at(px, 29, 20); - ensure(named(reverse, "the edge over the glow: red " + std::to_string(edge.r)), - std::fabs((F32)edge.r - 255.f * Outline::INNER_OPACITY) <= 2.f); - ensure(named(reverse, "the glow under it: green " + std::to_string(edge.g)), - std::fabs((F32)edge.g - 255.f * glow * (1.f - Outline::INNER_OPACITY)) <= 2.f); - ensure(named(reverse, "the glow over A's surface, which lies behind it"), green(at(px, 20, 21))); + UNSCOPED_INFO("the edge over the glow: red " << (U32)edge.r); + REQUIRE(std::fabs((F32)edge.r - 255.f * Outline::INNER_OPACITY) <= 2.f); + UNSCOPED_INFO("the glow under it: green " << (U32)edge.g); + REQUIRE(std::fabs((F32)edge.g - 255.f * glow * (1.f - Outline::INNER_OPACITY)) <= 2.f); + UNSCOPED_INFO("the glow over A's surface, which lies behind it"); + REQUIRE(green(at(px, 20, 21))); } } // A glowing object's hidden parts do not glow: its halo follows the part the scene shows. - template<> template<> - void alselectionoutline_object_t::test<36>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a glowing object's halo follows only the part the scene shows", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); // A, columns 9 to 31, behind something in the scene over columns 0 to 20. occlude(3.f, 0, 0, 21, H); idPass({ hoverDraw(quadA(), ID_A) }); const std::vector ids = read(mIdMap); - ensure_equals(named(reverse, "its hidden part left out"), (U32)at(ids, 12, 32).b, 0U); + UNSCOPED_INFO("its hidden part left out"); + REQUIRE((U32)at(ids, 12, 32).b == 0U); palette({ { ID_A, GREEN } }, false); const std::vector px = edgePass(glowView(6), nullptr, false, nullptr, true); - ensure(named(reverse, "a glow past its visible side"), green(at(px, 32, 32)) && at(px, 32, 32).g > 200); - ensure(named(reverse, "none past its hidden one"), black(at(px, 8, 32)) && black(at(px, 5, 32))); - ensure(named(reverse, "nor above it"), black(at(px, 12, 46))); - ensure(named(reverse, "but above the part shown"), green(at(px, 26, 45))); + { + INFO("a glow past its visible side"); + REQUIRE(green(at(px, 32, 32))); + REQUIRE(at(px, 32, 32).g > 200); + } + { + INFO("none past its hidden one"); + REQUIRE(black(at(px, 8, 32))); + REQUIRE(black(at(px, 5, 32))); + } + UNSCOPED_INFO("nor above it"); + REQUIRE(black(at(px, 12, 46))); + UNSCOPED_INFO("but above the part shown"); + REQUIRE(green(at(px, 26, 45))); } } // The wireframe draws no lines for what glows, beside a selection whose lines it draws. - template<> template<> - void alselectionoutline_object_t::test<37>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: the wireframe draws no lines for what glows beside a selection", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); palette({ { ID_A, RED }, { ID_B, GREEN } }, true); windowDepth(nullptr); @@ -2595,21 +3079,23 @@ namespace tut glowing += p.g > 0 ? 1 : 0; } } - ensure(named(reverse, "the selection's lines"), selected > 20); - ensure_equals(named(reverse, "none for what glows"), glowing, size_t(0)); + UNSCOPED_INFO("the selection's lines"); + REQUIRE(selected > 20); + UNSCOPED_INFO("none for what glows"); + REQUIRE(glowing == size_t(0)); } } // The tiles change nothing the glow draws, where it reaches past the lines; and a glowing object's glow lies in // its box grown by the glow's reach, which drawObject grows the scissor by. - template<> template<> - void alselectionoutline_object_t::test<38>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: the tiles and the scissor change nothing the glow draws", "[ALSelectionOutline]") { constexpr U32 ID_GLOW = 400; constexpr U32 ID_FRONT = 401; constexpr S32 RADIUS = 9; for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); palette({ { ID_A, RED }, { ID_B, RED }, { ID_GLOW, GREEN }, { ID_FRONT, GREEN } }, true); // Two selections, a glowing object apart from them, and another nearer, over a corner of one. @@ -2617,106 +3103,131 @@ namespace tut hoverDraw(quad(44.f, 58.f, 22.f, 36.f, -1.f, -1.f), ID_FRONT) }); const std::vector tiled = edgePass(glowView(RADIUS), nullptr, false, nullptr, true); const std::vector untiled = edgePass(glowView(RADIUS), nullptr, true, nullptr, true); - ensure(named(reverse, "the glow reaches past the lines"), green(at(tiled, 28, 15))); - ensure(named(reverse, "the tiles change nothing it draws"), tiled == untiled); + UNSCOPED_INFO("the glow reaches past the lines"); + REQUIRE(green(at(tiled, 28, 15))); + UNSCOPED_INFO("the tiles change nothing it draws"); + REQUIRE(tiled == untiled); const LLVector4a extents[2] = { LLVector4a(6.f, 6.f, PIXEL_Z), LLVector4a(22.f, 24.f, PIXEL_Z) }; LLRect rect; - ensure(named(reverse, "its box is on screen"), - Outline::scissorRect(pixelProjection(), extents, W, H, Outline::lineReach(RADIUS), rect)); + UNSCOPED_INFO("its box is on screen"); + REQUIRE(Outline::scissorRect(pixelProjection(), extents, W, H, Outline::lineReach(RADIUS), rect)); pixelIds({ hoverDraw(pixels(6, 22, 6, 24), ID_GLOW) }); const std::vector whole = edgePass(glowView(RADIUS), nullptr, false, nullptr, true); const std::vector scissored = edgePass(glowView(RADIUS), &rect, false, &rect, true); - ensure(named(reverse, "its glow is drawn"), lit(whole) > 100); - ensure(named(reverse, "and lies in its box grown by its reach"), whole == scissored); + UNSCOPED_INFO("its glow is drawn"); + REQUIRE(lit(whole) > 100); + UNSCOPED_INFO("and lies in its box grown by its reach"); + REQUIRE(whole == scissored); } } // A face wearing the library's transparent texture is drawn whole, legacy or GLTF, whatever its alpha mode, so // the invisible prims builders hide roots and touch areas with are outlined; any other texture keeps its alpha cut. - template<> template<> - void alselectionoutline_object_t::test<39>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a face wearing the library's transparent texture is drawn whole", "[ALSelectionOutline]") { const LLUUID other("c0ffee00-0000-4000-8000-0000000000aa"); const F32 blend = Outline::legacyAlphaCutoff(false, 0, 0, true); const F32 mask = Outline::gltfAlphaCutoff(LLGLTFMaterial::ALPHA_MODE_MASK, 0.5f); - ensure_equals("the transparent texture is drawn whole", Outline::faceAlphaCutoff(IMG_TRANSPARENT, blend), - Outline::NO_ALPHA_TEST); - ensure_equals("as a GLTF base colour too", Outline::faceAlphaCutoff(IMG_TRANSPARENT, mask), Outline::NO_ALPHA_TEST); - ensure_equals("any other keeps its cut", Outline::faceAlphaCutoff(other, blend), blend); - ensure_equals("a GLTF one too", Outline::faceAlphaCutoff(other, mask), mask); + UNSCOPED_INFO("the transparent texture is drawn whole"); + CHECK(Outline::faceAlphaCutoff(IMG_TRANSPARENT, blend) == Outline::NO_ALPHA_TEST); + UNSCOPED_INFO("as a GLTF base colour too"); + CHECK(Outline::faceAlphaCutoff(IMG_TRANSPARENT, mask) == Outline::NO_ALPHA_TEST); + UNSCOPED_INFO("any other keeps its cut"); + CHECK(Outline::faceAlphaCutoff(other, blend) == blend); + UNSCOPED_INFO("a GLTF one too"); + CHECK(Outline::faceAlphaCutoff(other, mask) == mask); for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); palette({ { ID_A, RED } }, true); for (const LLUUID& texture : { IMG_TRANSPARENT, other }) { const bool whole = texture == IMG_TRANSPARENT; - const std::string which = whole ? "the transparent texture" : "another clear texture"; + INFO((whole ? "the transparent texture" : "another clear texture")); idPass({ { quadA(), ID_A, Outline::PRIORITY_ROOT, Outline::faceAlphaCutoff(texture, blend), clearTexture() } }); const std::vector ids = read(mIdMap); - ensure_equals(named(reverse, which + (whole ? " drawn whole" : " cut away")), idAt(ids, 20, 32), - whole ? ID_A : 0U); + UNSCOPED_INFO((whole ? "drawn whole" : "cut away")); + REQUIRE(idAt(ids, 20, 32) == (whole ? ID_A : 0U)); const std::vector px = edgePass(); - ensure(named(reverse, which + (whole ? " outlined" : " not outlined")), - whole ? red(at(px, 8, 32)) : black(at(px, 8, 32))); + UNSCOPED_INFO((whole ? "outlined" : "not outlined")); + REQUIRE((whole ? red(at(px, 8, 32)) : black(at(px, 8, 32)))); } } } // On the HUD, an object in the id pass for its wireframe's depth alone is drawn nowhere there: its id is in the // target, hiding what lies behind it from the lines, and it has no contour while another object's is drawn. - template<> template<> - void alselectionoutline_object_t::test<40>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a HUD object in the id pass only for its wireframe's depth is not drawn", "[ALSelectionOutline]") { for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); Draw wire_only{ pixels(36, 50, 20, 44), ID_B }; wire_only.mParts = Outline::PART_WIREFRAME; const std::vector ids = pixelIds({ { pixels(10, 26, 20, 44), ID_A }, wire_only }); - ensure_equals(named(reverse, "its id is in the target"), idAt(ids, 40, 32), ID_B); - ensure_equals(named(reverse, "drawn nowhere"), (U32)at(ids, 40, 32).b, 0U); - ensure_equals(named(reverse, "the other drawn"), (U32)at(ids, 20, 32).b, 255U); + UNSCOPED_INFO("its id is in the target"); + REQUIRE(idAt(ids, 40, 32) == ID_B); + UNSCOPED_INFO("drawn nowhere"); + REQUIRE((U32)at(ids, 40, 32).b == 0U); + UNSCOPED_INFO("the other drawn"); + REQUIRE((U32)at(ids, 20, 32).b == 255U); palette({ { ID_A, RED }, { ID_B, GREEN } }, true); const std::vector px = edgePass(Outline::makeView(identity(), pixelProjection(), TEST_WIDTH, 1.f, true)); - ensure(named(reverse, "the other's contour"), red(at(px, 9, 32)) && red(at(px, 26, 32))); - ensure(named(reverse, "none around it"), black(at(px, 35, 32)) && black(at(px, 50, 32)) && black(at(px, 43, 45))); + { + INFO("the other's contour"); + REQUIRE(red(at(px, 9, 32))); + REQUIRE(red(at(px, 26, 32))); + } + { + INFO("none around it"); + REQUIRE(black(at(px, 35, 32))); + REQUIRE(black(at(px, 50, 32))); + REQUIRE(black(at(px, 43, 45))); + } } } // A blended face is cut only where its texture is all but clear: one translucent all over, as a pane of glass is, // is drawn whole, outlined and wireframed, where the same texture under a cutoff of a half is cut away. - template<> template<> - void alselectionoutline_object_t::test<41>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: a blended face is cut only where its texture is all but clear", "[ALSelectionOutline]") { const F32 blend = Outline::legacyAlphaCutoff(false, 0, 0, true); const F32 half = Outline::gltfAlphaCutoff(LLGLTFMaterial::ALPHA_MODE_MASK, 0.5f); for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); palette({ { ID_A, RED } }, true); const U32 glass = glassTexture(); idPass({ { quadA(), ID_A, Outline::PRIORITY_ROOT, blend, glass } }); std::vector ids = read(mIdMap); - ensure_equals(named(reverse, "a blended pane of glass drawn whole"), idAt(ids, 20, 32), ID_A); + UNSCOPED_INFO("a blended pane of glass drawn whole"); + REQUIRE(idAt(ids, 20, 32) == ID_A); std::vector px = edgePass(); - ensure(named(reverse, "and outlined"), red(at(px, 8, 32))); + UNSCOPED_INFO("and outlined"); + REQUIRE(red(at(px, 8, 32))); windowDepth(nullptr); px = worldWireframe({ { quadA(), ID_A, Outline::PRIORITY_ROOT, blend, glass } }); - ensure(named(reverse, "and wireframed"), lit(px) > 0); + UNSCOPED_INFO("and wireframed"); + REQUIRE(lit(px) > 0); idPass({ { quadA(), ID_A, Outline::PRIORITY_ROOT, half, glass } }); ids = read(mIdMap); - ensure_equals(named(reverse, "under a cutoff of a half it is cut away"), idAt(ids, 20, 32), 0U); + UNSCOPED_INFO("under a cutoff of a half it is cut away"); + REQUIRE(idAt(ids, 20, 32) == 0U); px = edgePass(); - ensure(named(reverse, "and not outlined"), black(at(px, 8, 32))); + UNSCOPED_INFO("and not outlined"); + REQUIRE(black(at(px, 8, 32))); px = worldWireframe({ { quadA(), ID_A, Outline::PRIORITY_ROOT, half, glass } }); - ensure_equals(named(reverse, "nor wireframed"), lit(px), size_t(0)); + UNSCOPED_INFO("nor wireframed"); + REQUIRE(lit(px) == size_t(0)); } } @@ -2724,14 +3235,14 @@ namespace tut // around shapes thin, slanted and cut off by the target's edges, between two glowing objects, where the lower id's // colour is drawn where both are as near, beside selections and with one in front of it, and around an object // partly hidden. - template<> template<> - void alselectionoutline_object_t::test<42>() + TEST_CASE_METHOD(alselectionoutline_data, "ALSelectionOutline: the glow reach and row passes draw what the full search draws", "[ALSelectionOutline]") { constexpr U32 ID_GLOW = 400; constexpr U32 ID_OTHER = 401; const LLColor4 BLUE(0.f, 0.f, 1.f, 1.f); for (bool reverse : conventions()) { + CAPTURE(reverse); setUp(reverse); palette({ { ID_A, RED }, { ID_B, RED }, { ID_GLOW, GREEN }, { ID_OTHER, BLUE } }, true); @@ -2752,36 +3263,44 @@ namespace tut }; for (const auto& [name, draws] : scenes) { + INFO(name); pixelIds(draws); for (S32 radius : { 1, 6, 9, 17, 33 }) { - const std::string at_radius = name + ", a reach of " + std::to_string(radius); + CAPTURE(radius); const std::vector rows = edgePass(glowView(radius), nullptr, false, nullptr, true); const std::vector full = edgePass(glowView(radius), nullptr, true, nullptr, true); - ensure(named(reverse, at_radius + ": it glows"), lit(rows) > 0); - ensure(named(reverse, at_radius + ": as the full search draws it"), rows == full); + UNSCOPED_INFO("it glows"); + REQUIRE(lit(rows) > 0); + UNSCOPED_INFO("as the full search draws it"); + REQUIRE(rows == full); } } // Column 24 is five columns from each of the first scene's objects, column 23 four from the one on the left. pixelIds(scenes[0].second); const std::vector between = edgePass(glowView(9), nullptr, false, nullptr, true); - ensure(named(reverse, "as near both along a row, the lower id's colour"), green(at(between, 24, 32))); - ensure(named(reverse, "nearer the left, its colour"), blue(at(between, 23, 32))); + UNSCOPED_INFO("as near both along a row, the lower id's colour"); + REQUIRE(green(at(between, 24, 32))); + UNSCOPED_INFO("nearer the left, its colour"); + REQUIRE(blue(at(between, 23, 32))); pixelIds(scenes[4].second); const std::vector across = edgePass(glowView(9), nullptr, false, nullptr, true); - ensure(named(reverse, "as near both along a column and a row, the lower id's colour"), green(at(across, 30, 30))); + UNSCOPED_INFO("as near both along a column and a row, the lower id's colour"); + REQUIRE(green(at(across, 30, 30))); // Behind something in the scene over columns 0 to 20, its hidden part drawing no glow. occlude(3.f, 0, 0, 21, H); idPass({ hoverDraw(quadA(), ID_GLOW) }); for (S32 radius : { 6, 17 }) { - const std::string at_radius = "partly hidden, a reach of " + std::to_string(radius); + CAPTURE(radius); const std::vector rows = edgePass(glowView(radius), nullptr, false, nullptr, true); const std::vector full = edgePass(glowView(radius), nullptr, true, nullptr, true); - ensure(named(reverse, at_radius + ": it glows"), lit(rows) > 0); - ensure(named(reverse, at_radius + ": as the full search draws it"), rows == full); + UNSCOPED_INFO("partly hidden, it glows"); + REQUIRE(lit(rows) > 0); + UNSCOPED_INFO("partly hidden, as the full search draws it"); + REQUIRE(rows == full); } } } diff --git a/indra/newview/tests/alterrainsurfacemaps_test.cpp b/indra/newview/tests/alterrainsurfacemaps_test.cpp index 1c224de1ee..52e0cb6b1a 100644 --- a/indra/newview/tests/alterrainsurfacemaps_test.cpp +++ b/indra/newview/tests/alterrainsurfacemaps_test.cpp @@ -26,14 +26,15 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include +#include "altestmatchers.h" #include "../alterrainsurfacemaps.h" #include #include -namespace tut +namespace { struct alterrainsurfacemaps_data { @@ -54,148 +55,169 @@ namespace tut } }; - typedef test_group alterrainsurfacemaps_test; - typedef alterrainsurfacemaps_test::object alterrainsurfacemaps_object; - tut::alterrainsurfacemaps_test alterrainsurfacemaps_testcase("ALTerrainSurfaceMaps"); - // fill() visits every grid coordinate from -APRON to gpe + APRON - 1 on // both axes, once, rows along y, and lands each at (gx + APRON, gy + APRON). - template<> template<> - void alterrainsurfacemaps_object::test<1>() + TEST_CASE_METHOD(alterrainsurfacemaps_data, "ALTerrainSurfaceMaps: fill() lays the grid out APRON in with rows along y", "[ALTerrainSurfaceMaps]") { std::vector out; ALTerrainSurfaceMaps::fill(out, GPE, 1, [](S32 gx, S32 gy, F32* t) { t[0] = (F32)(gx * 100 + gy); }); - ensure_equals("size", out.size(), static_cast(SIZE) * SIZE); - ensure_equals("first texel is the south-west apron corner", out[texel(-APRON, -APRON)], (F32)(-APRON * 100 - APRON)); - ensure_equals("grid origin sits APRON in", out[texel(0, 0)], 0.f); - ensure_equals("buffer column", out[texel(GPE - 1, 3)], (F32)((GPE - 1) * 100 + 3)); - ensure_equals("last texel is the north-east apron corner", - out[texel(GPE + APRON - 1, GPE + APRON - 1)], (F32)((GPE + APRON - 1) * 100 + GPE + APRON - 1)); - ensure_equals("row stride is the map width", out[texel(0, 1)] - out[texel(0, 0)], 1.f); - ensure_equals("column stride is one texel", out[texel(1, 0)] - out[texel(0, 0)], 100.f); + REQUIRE(out.size() == static_cast(SIZE) * SIZE); + UNSCOPED_INFO("first texel is the south-west apron corner"); + CHECK(out[texel(-APRON, -APRON)] == (F32)(-APRON * 100 - APRON)); + UNSCOPED_INFO("grid origin sits APRON in"); + CHECK(out[texel(0, 0)] == 0.f); + UNSCOPED_INFO("buffer column"); + CHECK(out[texel(GPE - 1, 3)] == (F32)((GPE - 1) * 100 + 3)); + UNSCOPED_INFO("last texel is the north-east apron corner"); + CHECK(out[texel(GPE + APRON - 1, GPE + APRON - 1)] == (F32)((GPE + APRON - 1) * 100 + GPE + APRON - 1)); + UNSCOPED_INFO("row stride is the map width"); + CHECK(out[texel(0, 1)] - out[texel(0, 0)] == 1.f); + UNSCOPED_INFO("column stride is one texel"); + CHECK(out[texel(1, 0)] - out[texel(0, 0)] == 100.f); } // Two channels interleave, and a second fill of a buffer already the // right size keeps the channel the sampler does not write. - template<> template<> - void alterrainsurfacemaps_object::test<2>() + TEST_CASE_METHOD(alterrainsurfacemaps_data, "ALTerrainSurfaceMaps: two channels interleave and a refill keeps the channel it does not write", "[ALTerrainSurfaceMaps]") { std::vector out; ALTerrainSurfaceMaps::fill(out, GPE, 2, [](S32 gx, S32 gy, F32* t) { t[0] = (F32)gx; t[1] = (F32)gy; }); - ensure_equals("size", out.size(), static_cast(SIZE) * SIZE * 2); - ensure_equals("channel 0", out[texel(3, 5, 2)], 3.f); - ensure_equals("channel 1", out[texel(3, 5, 2) + 1], 5.f); + REQUIRE(out.size() == static_cast(SIZE) * SIZE * 2); + UNSCOPED_INFO("channel 0"); + CHECK(out[texel(3, 5, 2)] == 3.f); + UNSCOPED_INFO("channel 1"); + CHECK(out[texel(3, 5, 2) + 1] == 5.f); ALTerrainSurfaceMaps::fill(out, GPE, 2, [](S32 gx, S32, F32* t) { t[0] = (F32)(gx + 1000); }); - ensure_equals("channel 0 rewritten", out[texel(3, 5, 2)], 1003.f); - ensure_equals("channel 1 kept", out[texel(3, 5, 2) + 1], 5.f); + UNSCOPED_INFO("channel 0 rewritten"); + CHECK(out[texel(3, 5, 2)] == 1003.f); + UNSCOPED_INFO("channel 1 kept"); + CHECK(out[texel(3, 5, 2) + 1] == 5.f); } - // Inside the region nothing moves. - template<> template<> - void alterrainsurfacemaps_object::test<3>() + TEST_CASE_METHOD(alterrainsurfacemaps_data, "ALTerrainSurfaceMaps: resolve moves nothing inside the region", "[ALTerrainSurfaceMaps]") { bool has[8]; allSet(true, has); S32 gx = 4, gy = GPE - 1; - ensure_equals("interior is this surface", ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has), MIDDLE); - ensure_equals("gx unchanged", gx, 4); - ensure_equals("gy unchanged, the buffer row is ours", gy, GPE - 1); + UNSCOPED_INFO("interior is this surface"); + CHECK(ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has) == MIDDLE); + CHECK(gx == 4); + UNSCOPED_INFO("gy unchanged, the buffer row is ours"); + CHECK(gy == GPE - 1); } // One axis out, neighbour present: the neighbour's grid 0 is our last // column, so our -1 is their gpe - 2 and our gpe is their 1. - template<> template<> - void alterrainsurfacemaps_object::test<4>() + TEST_CASE_METHOD(alterrainsurfacemaps_data, "ALTerrainSurfaceMaps: one axis out resolves to that neighbour's grid", "[ALTerrainSurfaceMaps]") { bool has[8]; allSet(true, has); S32 gx = -1, gy = 3; - ensure_equals("west", ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has), WEST); - ensure_equals("west gx", gx, GPE - 2); - ensure_equals("west gy", gy, 3); + CHECK(ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has) == WEST); + UNSCOPED_INFO("west gx"); + CHECK(gx == GPE - 2); + UNSCOPED_INFO("west gy"); + CHECK(gy == 3); gx = -APRON; gy = 3; ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has); - ensure_equals("west, two out", gx, GPE - 3); + UNSCOPED_INFO("west, two out"); + CHECK(gx == GPE - 3); gx = GPE; gy = 3; - ensure_equals("east", ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has), EAST); - ensure_equals("east gx", gx, 1); + CHECK(ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has) == EAST); + UNSCOPED_INFO("east gx"); + CHECK(gx == 1); gx = GPE + 1; gy = 3; ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has); - ensure_equals("east, two out", gx, 2); + UNSCOPED_INFO("east, two out"); + CHECK(gx == 2); gx = 3; gy = -1; - ensure_equals("south", ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has), SOUTH); - ensure_equals("south gy", gy, GPE - 2); + CHECK(ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has) == SOUTH); + UNSCOPED_INFO("south gy"); + CHECK(gy == GPE - 2); gx = 3; gy = GPE + 1; - ensure_equals("north", ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has), NORTH); - ensure_equals("north gy", gy, 2); + CHECK(ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has) == NORTH); + UNSCOPED_INFO("north gy"); + CHECK(gy == 2); } - // Both axes out: the diagonal neighbour when it is there. - template<> template<> - void alterrainsurfacemaps_object::test<5>() + TEST_CASE_METHOD(alterrainsurfacemaps_data, "ALTerrainSurfaceMaps: both axes out resolve to the diagonal neighbour when it is there", "[ALTerrainSurfaceMaps]") { bool has[8]; allSet(true, has); S32 gx = -1, gy = -APRON; - ensure_equals("south-west", ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has), SOUTHWEST); - ensure_equals("south-west gx", gx, GPE - 2); - ensure_equals("south-west gy", gy, GPE - 3); + CHECK(ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has) == SOUTHWEST); + UNSCOPED_INFO("south-west gx"); + CHECK(gx == GPE - 2); + UNSCOPED_INFO("south-west gy"); + CHECK(gy == GPE - 3); gx = GPE + 1; gy = GPE; - ensure_equals("north-east", ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has), NORTHEAST); - ensure_equals("north-east gx", gx, 2); - ensure_equals("north-east gy", gy, 1); + CHECK(ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has) == NORTHEAST); + UNSCOPED_INFO("north-east gx"); + CHECK(gx == 2); + UNSCOPED_INFO("north-east gy"); + CHECK(gy == 1); } // A missing neighbour clamps the axis that needed it and keeps the rest of // the walk: no west means gx clamps to 0 and y still resolves north. - template<> template<> - void alterrainsurfacemaps_object::test<6>() + TEST_CASE_METHOD(alterrainsurfacemaps_data, "ALTerrainSurfaceMaps: a missing neighbour clamps its axis and the other still resolves", "[ALTerrainSurfaceMaps]") { bool has[8]; allSet(true, has); has[WEST] = false; S32 gx = -1, gy = 3; - ensure_equals("no west: this surface", ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has), MIDDLE); - ensure_equals("no west: gx clamped", gx, 0); + UNSCOPED_INFO("no west: this surface"); + CHECK(ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has) == MIDDLE); + UNSCOPED_INFO("no west: gx clamped"); + CHECK(gx == 0); gx = -1; gy = GPE; - ensure_equals("no west, north present: north", ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has), NORTH); - ensure_equals("north with gx clamped", gx, 0); - ensure_equals("north gy", gy, 1); + UNSCOPED_INFO("no west, north present: north"); + CHECK(ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has) == NORTH); + UNSCOPED_INFO("north with gx clamped"); + CHECK(gx == 0); + UNSCOPED_INFO("north gy"); + CHECK(gy == 1); allSet(true, has); has[NORTHWEST] = false; gx = -1; gy = GPE; - ensure_equals("west present, north-west missing: west", ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has), WEST); - ensure_equals("west gx", gx, GPE - 2); - ensure_equals("west gy clamped to its last row", gy, GPE - 1); + UNSCOPED_INFO("west present, north-west missing: west"); + CHECK(ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has) == WEST); + UNSCOPED_INFO("west gx"); + CHECK(gx == GPE - 2); + UNSCOPED_INFO("west gy clamped to its last row"); + CHECK(gy == GPE - 1); allSet(false, has); gx = GPE + 1; gy = -1; - ensure_equals("no neighbours: this surface", ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has), MIDDLE); - ensure_equals("gx clamped to the buffer column", gx, GPE - 1); - ensure_equals("gy clamped to 0", gy, 0); + UNSCOPED_INFO("no neighbours: this surface"); + CHECK(ALTerrainSurfaceMaps::resolve(gx, gy, GPE, has) == MIDDLE); + UNSCOPED_INFO("gx clamped to the buffer column"); + CHECK(gx == GPE - 1); + UNSCOPED_INFO("gy clamped to 0"); + CHECK(gy == 0); } // The smooth surface passes through every sample: at an integer grid point the // value is the sample, whatever the neighbours do. - template<> template<> - void alterrainsurfacemaps_object::test<7>() + TEST_CASE_METHOD(alterrainsurfacemaps_data, "ALTerrainSurfaceMaps: smoothHeight passes through every sample", "[ALTerrainSurfaceMaps]") { auto field = [](S32 gx, S32 gy) { return (F32)(gx * gx - 3 * gy + (gx * gy) % 5); }; for (S32 gy = 0; gy < 6; ++gy) { for (S32 gx = 0; gx < 6; ++gx) { + CAPTURE(gx, gy); const F32 h = ALTerrainSurfaceMaps::smoothHeight((F32)gx, (F32)gy, field, nullptr, nullptr); - ensure_equals("value at a sample is the sample", h, field(gx, gy)); + REQUIRE(h == field(gx, gy)); } } } @@ -205,30 +227,41 @@ namespace tut // drop inside the cell's range; a Catmull-Rom would put metres of bump // above the crease. And a monotone run stays monotone, so a ramp is a // ramp with no ripple ahead of or behind the step. - template<> template<> - void alterrainsurfacemaps_object::test<8>() + TEST_CASE_METHOD(alterrainsurfacemaps_data, "ALTerrainSurfaceMaps: smoothHeight grows no lip at a step and stays monotone down it", "[ALTerrainSurfaceMaps]") { auto cliff = [](S32 gx, S32) { return gx <= 2 ? 30.f : 0.f; }; for (F32 x = 0.f; x < 2.f; x += 0.125f) { + CAPTURE(x); F32 dzdx = 1.f, dzdy = 1.f; - ensure_approximately_equals("level up to the edge", ALTerrainSurfaceMaps::smoothHeight(x, 3.f, cliff, &dzdx, &dzdy), 30.f, 12); + UNSCOPED_INFO("level up to the edge"); + REQUIRE_THAT(ALTerrainSurfaceMaps::smoothHeight(x, 3.f, cliff, &dzdx, &dzdy), ll_test::WithinBits(30.f, 12)); // The tangent at the crease sample is zeroed, not averaged with the drop: the // level run has no slope at all, so it does not lean toward the edge. - ensure_approximately_equals("no slope on the level run", dzdx, 0.f, 12); + UNSCOPED_INFO("no slope on the level run"); + REQUIRE_THAT(dzdx, ll_test::WithinBits(0.f, 12)); } F32 previous = 30.f; for (F32 x = 2.f; x <= 3.f; x += 0.0625f) { + CAPTURE(x); const F32 h = ALTerrainSurfaceMaps::smoothHeight(x, 3.f, cliff, nullptr, nullptr); - ensure("inside the cell's range", h <= 30.f && h >= 0.f); - ensure("monotone down the face", h <= previous); + { + INFO("inside the cell's range"); + REQUIRE(h <= 30.f); + REQUIRE(h >= 0.f); + } + UNSCOPED_INFO("monotone down the face"); + REQUIRE(h <= previous); previous = h; } - ensure_approximately_equals("mid-cell is the midpoint of the step", ALTerrainSurfaceMaps::smoothHeight(2.5f, 3.f, cliff, nullptr, nullptr), 15.f, 12); + UNSCOPED_INFO("mid-cell is the midpoint of the step"); + CHECK_THAT(ALTerrainSurfaceMaps::smoothHeight(2.5f, 3.f, cliff, nullptr, nullptr), ll_test::WithinBits(15.f, 12)); for (F32 x = 3.f; x < 5.f; x += 0.125f) { - ensure_approximately_equals("level past the foot", ALTerrainSurfaceMaps::smoothHeight(x, 3.f, cliff, nullptr, nullptr), 0.f, 12); + CAPTURE(x); + UNSCOPED_INFO("level past the foot"); + REQUIRE_THAT(ALTerrainSurfaceMaps::smoothHeight(x, 3.f, cliff, nullptr, nullptr), ll_test::WithinBits(0.f, 12)); } // Off the axis too: a corner of the plateau, evaluated across the @@ -238,28 +271,29 @@ namespace tut { for (F32 x = 2.f; x <= 3.f; x += 0.25f) { + CAPTURE(x, y); const F32 h = ALTerrainSurfaceMaps::smoothHeight(x, y, corner, nullptr, nullptr); - ensure("plateau corner within its cell's range", h >= 0.f && h <= 30.f); + INFO("plateau corner within its cell's range"); + REQUIRE(h >= 0.f); + REQUIRE(h <= 30.f); } } } - // A field symmetric in x and y gives a surface symmetric in x and y. - template<> template<> - void alterrainsurfacemaps_object::test<9>() + TEST_CASE_METHOD(alterrainsurfacemaps_data, "ALTerrainSurfaceMaps: a field symmetric in x and y gives a surface symmetric in x and y", "[ALTerrainSurfaceMaps]") { auto field = [](S32 gx, S32 gy) { return (F32)(gx * gx + gy * gy) * 0.1f + (F32)((gx + gy) % 3); }; for (F32 v = 0.f; v < 4.f; v += 0.37f) { + CAPTURE(v); const F32 a = ALTerrainSurfaceMaps::smoothHeight(1.2f, v, field, nullptr, nullptr); const F32 b = ALTerrainSurfaceMaps::smoothHeight(v, 1.2f, field, nullptr, nullptr); - ensure_approximately_equals("h(x, y) == h(y, x)", a, b, 5); + REQUIRE_THAT(a, ll_test::WithinBits(b, 5)); } } // The analytic gradient is the derivative of the value. - template<> template<> - void alterrainsurfacemaps_object::test<10>() + TEST_CASE_METHOD(alterrainsurfacemaps_data, "ALTerrainSurfaceMaps: the analytic gradient matches a central difference of the value", "[ALTerrainSurfaceMaps]") { auto field = [](S32 gx, S32 gy) { return sinf(0.7f * gx) * 2.f + cosf(0.4f * gy) + 0.1f * gx * gy; }; const F32 eps = 1e-2f; @@ -267,14 +301,15 @@ namespace tut { for (F32 x = 0.6f; x < 4.f; x += 0.9f) { + CAPTURE(x, y); F32 dzdx, dzdy; ALTerrainSurfaceMaps::smoothHeight(x, y, field, &dzdx, &dzdy); const F32 fdx = (ALTerrainSurfaceMaps::smoothHeight(x + eps, y, field, nullptr, nullptr) - ALTerrainSurfaceMaps::smoothHeight(x - eps, y, field, nullptr, nullptr)) / (2.f * eps); const F32 fdy = (ALTerrainSurfaceMaps::smoothHeight(x, y + eps, field, nullptr, nullptr) - ALTerrainSurfaceMaps::smoothHeight(x, y - eps, field, nullptr, nullptr)) / (2.f * eps); - ensure_approximately_equals("dz/dx", dzdx, fdx, 8); - ensure_approximately_equals("dz/dy", dzdy, fdy, 8); + REQUIRE_THAT(dzdx, ll_test::WithinBits(fdx, 8)); + REQUIRE_THAT(dzdy, ll_test::WithinBits(fdy, 8)); } } } diff --git a/indra/newview/tests/altexturetable_test.cpp b/indra/newview/tests/altexturetable_test.cpp index 56e4018bc9..88194be953 100644 --- a/indra/newview/tests/altexturetable_test.cpp +++ b/indra/newview/tests/altexturetable_test.cpp @@ -24,7 +24,7 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../altexturetable.h" @@ -71,7 +71,7 @@ namespace using Table = ALTextureTable; } -namespace tut +namespace { struct texturetable_data { @@ -87,7 +87,8 @@ namespace tut for (U32 i = 1; i <= count; ++i) { Item* item = new Item(i); - ensure("fill insert", mTable.insert(key(i), item)); + CAPTURE(i); + REQUIRE(mTable.insert(key(i), item)); items.push_back(item); } return items; @@ -103,232 +104,237 @@ namespace tut static std::vector ids(std::initializer_list in) { return std::vector(in); } }; - typedef test_group texturetable_group; - typedef texturetable_group::object texturetable_object; - tut::texturetable_group tg("ALTextureTable"); - // --- membership ---------------------------------------------------------- - // An empty table answers every query without touching anything. - template<> template<> - void texturetable_object::test<1>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: an empty table answers every query without touching anything", "[ALTextureTable]") { - ensure_equals("size", mTable.size(), (size_t)0); - ensure("empty", mTable.empty()); - ensure("find", mTable.find(key(1)) == nullptr); - ensure("erase", mTable.erase(key(1)).isNull()); - ensure_equals("window", window(4).size(), (size_t)0); + CHECK(mTable.size() == (size_t)0); + CHECK(mTable.empty()); + CHECK(mTable.find(key(1)) == nullptr); + CHECK(mTable.erase(key(1)).isNull()); + CHECK(window(4).size() == (size_t)0); mTable.rewind(3); - ensure_equals("cursor", mTable.cursor(), 0u); - ensure("consistent", mTable.consistent()); + CHECK(mTable.cursor() == 0u); + CHECK(mTable.consistent()); } // Insert records each item's position, find answers by key, iteration is // insertion order, and the table holds exactly one reference per item. - template<> template<> - void texturetable_object::test<2>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: insert records positions and iteration is insertion order", "[ALTextureTable]") { mTable.reserve(8); std::vector items = fill(3); - ensure_equals("size", mTable.size(), (size_t)3); - ensure("find 2", mTable.find(key(2)) == items[1]); - ensure("find type", mTable.find(key(2, 1)) == nullptr); - ensure("contains", mTable.contains(key(3))); + REQUIRE(mTable.size() == (size_t)3); + CHECK(mTable.find(key(2)) == items[1]); + UNSCOPED_INFO("the same id under another type is another key"); + CHECK(mTable.find(key(2, 1)) == nullptr); + CHECK(mTable.contains(key(3))); for (S32 i = 0; i < 3; ++i) { - ensure_equals("index", items[i]->getListIndex(), i); - ensure("position", mTable[i] == items[i]); - ensure_equals("one ref", items[i]->getNumRefs(), 1); + CAPTURE(i); + REQUIRE(items[i]->getListIndex() == i); + REQUIRE(mTable[i] == items[i]); + REQUIRE(items[i]->getNumRefs() == 1); } U32 expected = 1; for (const LLPointer& item : mTable) { - ensure_equals("iteration order", item->mId, expected++); + REQUIRE(item->mId == expected++); } - ensure_equals("live", Item::sLive, 3); - ensure("consistent", mTable.consistent()); + CHECK(Item::sLive == 3); + CHECK(mTable.consistent()); } // A key already present, or an item already in a table, is refused and // nothing changes; the refused item keeps its own state. - template<> template<> - void texturetable_object::test<3>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: a duplicate key or an item already in a table is refused", "[ALTextureTable]") { std::vector items = fill(3); LLPointer other = new Item(9); - ensure("duplicate key refused", !mTable.insert(key(2), other)); - ensure_equals("size after key refusal", mTable.size(), (size_t)3); - ensure("original kept", mTable.find(key(2)) == items[1]); - ensure_equals("refused item index", other->getListIndex(), -1); - ensure_equals("refused item refs", other->getNumRefs(), 1); - - ensure("second key refused", !mTable.insert(key(9), items[0])); - ensure("second key absent", mTable.find(key(9)) == nullptr); - ensure_equals("size after item refusal", mTable.size(), (size_t)3); - ensure("consistent", mTable.consistent()); + UNSCOPED_INFO("duplicate key refused"); + CHECK_FALSE(mTable.insert(key(2), other)); + CHECK(mTable.size() == (size_t)3); + UNSCOPED_INFO("original kept"); + CHECK(mTable.find(key(2)) == items[1]); + CHECK(other->getListIndex() == -1); + CHECK(other->getNumRefs() == 1); + + UNSCOPED_INFO("an item already in a table refused under a new key"); + CHECK_FALSE(mTable.insert(key(9), items[0])); + CHECK(mTable.find(key(9)) == nullptr); + CHECK(mTable.size() == (size_t)3); + CHECK(mTable.consistent()); } - // Erasing the last item needs no patching and hands the reference back. - template<> template<> - void texturetable_object::test<4>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: erasing the last item needs no patching and hands the reference back", "[ALTextureTable]") { std::vector items = fill(3); { LLPointer removed = mTable.erase(key(3)); - ensure("returned item", removed == items[2]); - ensure_equals("returned ref is the only one", removed->getNumRefs(), 1); - ensure_equals("removed index", removed->getListIndex(), -1); - ensure_equals("size", mTable.size(), (size_t)2); - ensure("gone", mTable.find(key(3)) == nullptr); - ensure_equals("others untouched 0", items[0]->getListIndex(), 0); - ensure_equals("others untouched 1", items[1]->getListIndex(), 1); - ensure_equals("still live", Item::sLive, 3); + REQUIRE(removed == items[2]); + UNSCOPED_INFO("returned ref is the only one"); + CHECK(removed->getNumRefs() == 1); + CHECK(removed->getListIndex() == -1); + CHECK(mTable.size() == (size_t)2); + CHECK(mTable.find(key(3)) == nullptr); + { + INFO("others untouched"); + CHECK(items[0]->getListIndex() == 0); + CHECK(items[1]->getListIndex() == 1); + } + UNSCOPED_INFO("still live while the returned ref is held"); + CHECK(Item::sLive == 3); } - ensure_equals("destroyed with the last ref", Item::sLive, 2); - ensure("consistent", mTable.consistent()); + UNSCOPED_INFO("destroyed with the last ref"); + CHECK(Item::sLive == 2); + CHECK(mTable.consistent()); } // Erasing a middle item moves the last item into the hole and the moved // item's recorded position follows it. - template<> template<> - void texturetable_object::test<5>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: erasing a middle item moves the last into the hole with its position", "[ALTextureTable]") { std::vector items = fill(5); LLPointer removed = mTable.erase(key(2)); - ensure("returned item", removed == items[1]); - ensure_equals("size", mTable.size(), (size_t)4); - ensure("last item fills the hole", mTable[1] == items[4]); - ensure_equals("moved index follows", items[4]->getListIndex(), 1); - ensure("moved item still found", mTable.find(key(5)) == items[4]); - ensure("erased key gone", mTable.find(key(2)) == nullptr); - ensure_equals("unmoved 0", items[0]->getListIndex(), 0); - ensure_equals("unmoved 2", items[2]->getListIndex(), 2); - ensure_equals("unmoved 3", items[3]->getListIndex(), 3); - ensure("consistent", mTable.consistent()); + CHECK(removed == items[1]); + REQUIRE(mTable.size() == (size_t)4); + UNSCOPED_INFO("last item fills the hole"); + CHECK(mTable[1] == items[4]); + UNSCOPED_INFO("moved index follows"); + CHECK(items[4]->getListIndex() == 1); + UNSCOPED_INFO("moved item still found"); + CHECK(mTable.find(key(5)) == items[4]); + CHECK(mTable.find(key(2)) == nullptr); + { + INFO("the others unmoved"); + CHECK(items[0]->getListIndex() == 0); + CHECK(items[2]->getListIndex() == 2); + CHECK(items[3]->getListIndex() == 3); + } + CHECK(mTable.consistent()); } - // An absent key erases nothing. - template<> template<> - void texturetable_object::test<6>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: an absent key erases nothing", "[ALTextureTable]") { std::vector items = fill(3); - ensure("null", mTable.erase(key(42)).isNull()); - ensure("null by type", mTable.erase(key(2, 1)).isNull()); - ensure_equals("size", mTable.size(), (size_t)3); - ensure_equals("live", Item::sLive, 3); - ensure("consistent", mTable.consistent()); + CHECK(mTable.erase(key(42)).isNull()); + UNSCOPED_INFO("a present id under another type"); + CHECK(mTable.erase(key(2, 1)).isNull()); + CHECK(mTable.size() == (size_t)3); + CHECK(Item::sLive == 3); + CHECK(mTable.consistent()); } // An insert after an erase lands at the end, not in the hole the erase // already filled. - template<> template<> - void texturetable_object::test<7>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: an insert after an erase lands at the end", "[ALTextureTable]") { std::vector items = fill(4); mTable.erase(key(2)); Item* added = new Item(6); - ensure("insert", mTable.insert(key(6), added)); - ensure_equals("at the end", added->getListIndex(), 3); - ensure_equals("size", mTable.size(), (size_t)4); - ensure("find", mTable.find(key(6)) == added); - ensure("consistent", mTable.consistent()); + CHECK(mTable.insert(key(6), added)); + UNSCOPED_INFO("at the end"); + CHECK(added->getListIndex() == 3); + CHECK(mTable.size() == (size_t)4); + CHECK(mTable.find(key(6)) == added); + CHECK(mTable.consistent()); } // --- the round-robin window ---------------------------------------------- - // Consecutive windows continue from the cursor and wrap. - template<> template<> - void texturetable_object::test<8>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: consecutive windows continue from the cursor and wrap", "[ALTextureTable]") { fill(5); - ensure("first", window(2) == ids({ 1, 2 })); - ensure("second", window(2) == ids({ 3, 4 })); - ensure("wraps", window(2) == ids({ 5, 1 })); - ensure_equals("cursor", mTable.cursor(), 1u); + CHECK(window(2) == ids({ 1, 2 })); + CHECK(window(2) == ids({ 3, 4 })); + UNSCOPED_INFO("wraps"); + CHECK(window(2) == ids({ 5, 1 })); + CHECK(mTable.cursor() == 1u); } - // A window larger than the table visits every item once. - template<> template<> - void texturetable_object::test<9>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: a window larger than the table visits every item once", "[ALTextureTable]") { fill(5); std::vector seen; U32 visited = mTable.visitWindow(10, [&](Item* item) { seen.push_back(item->mId); }); - ensure_equals("visited", visited, 5u); - ensure("each once", seen == ids({ 1, 2, 3, 4, 5 })); - ensure_equals("cursor back at start", mTable.cursor(), 0u); + CHECK(visited == 5u); + CHECK(seen == ids({ 1, 2, 3, 4, 5 })); + UNSCOPED_INFO("cursor back at start"); + CHECK(mTable.cursor() == 0u); } // When erases leave the cursor past the end, the next window starts over // instead of reading past the vector. - template<> template<> - void texturetable_object::test<10>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: a cursor left past the end by erases starts the next window over", "[ALTextureTable]") { fill(5); window(4); - ensure_equals("cursor", mTable.cursor(), 4u); + CHECK(mTable.cursor() == 4u); mTable.erase(key(1)); mTable.erase(key(2)); - ensure_equals("size", mTable.size(), (size_t)3); - ensure("consistent", mTable.consistent()); + CHECK(mTable.size() == (size_t)3); + CHECK(mTable.consistent()); - ensure("restarts", window(1) == ids({ 5 })); - ensure_equals("cursor after restart", mTable.cursor(), 1u); + UNSCOPED_INFO("restarts"); + CHECK(window(1) == ids({ 5 })); + CHECK(mTable.cursor() == 1u); } // Rewind steps the cursor back so unprocessed items come around again, // and clamps at one full turn. - template<> template<> - void texturetable_object::test<11>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: rewind steps the cursor back and clamps at one full turn", "[ALTextureTable]") { fill(5); window(4); mTable.rewind(2); - ensure_equals("cursor", mTable.cursor(), 2u); - ensure("revisits", window(1) == ids({ 3 })); + CHECK(mTable.cursor() == 2u); + UNSCOPED_INFO("revisits"); + CHECK(window(1) == ids({ 3 })); mTable.rewind(99); - ensure_equals("clamped to one turn", mTable.cursor(), 3u); - ensure("continues", window(1) == ids({ 4 })); + UNSCOPED_INFO("clamped to one turn"); + CHECK(mTable.cursor() == 3u); + UNSCOPED_INFO("continues"); + CHECK(window(1) == ids({ 4 })); } // An erase between windows changes who sits where, but every item the // next window hands out is a member, and the moved item is reached on the // following turn. - template<> template<> - void texturetable_object::test<12>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: after an erase between windows the moved item still comes around", "[ALTextureTable]") { fill(6); - ensure("first", window(3) == ids({ 1, 2, 3 })); + CHECK(window(3) == ids({ 1, 2, 3 })); mTable.erase(key(2)); - ensure("consistent", mTable.consistent()); + CHECK(mTable.consistent()); std::vector second = window(3); - ensure("second", second == ids({ 4, 5, 1 })); + CHECK(second == ids({ 4, 5, 1 })); for (U32 id : second) { - ensure("member", mTable.find(key(id)) != nullptr); + CAPTURE(id); + REQUIRE(mTable.find(key(id)) != nullptr); } - ensure("moved item comes around", window(3) == ids({ 6, 3, 4 })); + UNSCOPED_INFO("moved item comes around"); + CHECK(window(3) == ids({ 6, 3, 4 })); } - // Visiting hands out raw pointers and takes no references. - template<> template<> - void texturetable_object::test<13>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: visiting hands out raw pointers and takes no references", "[ALTextureTable]") { std::vector items = fill(3); window(3); for (Item* item : items) { - ensure_equals("refs after visit", item->getNumRefs(), 1); + CAPTURE(item->mId); + REQUIRE(item->getNumRefs() == 1); } } @@ -336,28 +342,27 @@ namespace tut // Clear resets every index before releasing, so an item that outlives the // table reads as in no table. - template<> template<> - void texturetable_object::test<14>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: clear leaves an item that outlives the table in no table", "[ALTextureTable]") { std::vector items = fill(3); LLPointer kept = items[1]; window(2); mTable.clear(); - ensure_equals("size", mTable.size(), (size_t)0); - ensure_equals("cursor", mTable.cursor(), 0u); - ensure_equals("kept index", kept->getListIndex(), -1); - ensure_equals("kept refs", kept->getNumRefs(), 1); - ensure_equals("others destroyed", Item::sLive, 1); - ensure("consistent", mTable.consistent()); - - ensure("reinsert after clear", mTable.insert(key(2), kept)); - ensure_equals("reinserted index", kept->getListIndex(), 0); + CHECK(mTable.size() == (size_t)0); + CHECK(mTable.cursor() == 0u); + CHECK(kept->getListIndex() == -1); + CHECK(kept->getNumRefs() == 1); + UNSCOPED_INFO("others destroyed"); + CHECK(Item::sLive == 1); + CHECK(mTable.consistent()); + + UNSCOPED_INFO("reinsert after clear"); + CHECK(mTable.insert(key(2), kept)); + CHECK(kept->getListIndex() == 0); } - // Destroying the table releases its references. - template<> template<> - void texturetable_object::test<15>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: destroying the table releases its references", "[ALTextureTable]") { { Table local; @@ -365,9 +370,9 @@ namespace tut { local.insert(key(i), new Item(i)); } - ensure_equals("live", Item::sLive, 3); + CHECK(Item::sLive == 3); } - ensure_equals("released", Item::sLive, 0); + CHECK(Item::sLive == 0); } // --- scale --------------------------------------------------------------- @@ -375,38 +380,43 @@ namespace tut // Growth through several rehashes keeps every item findable and every // recorded position right, and a reservation up front changes nothing // but the allocations. - template<> template<> - void texturetable_object::test<16>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: growth through several rehashes keeps every item findable and placed", "[ALTextureTable]") { const U32 count = 20000; std::vector items = fill(count); - ensure("consistent after growth", mTable.consistent()); + UNSCOPED_INFO("consistent after growth"); + CHECK(mTable.consistent()); for (U32 i = 1; i <= count; i += 997) { - ensure("found after growth", mTable.find(key(i)) == items[i - 1]); + CAPTURE(i); + REQUIRE(mTable.find(key(i)) == items[i - 1]); } // erase every third item, then look for all of them for (U32 i = 3; i <= count; i += 3) { - ensure("erase", mTable.erase(key(i)).notNull()); + CAPTURE(i); + REQUIRE(mTable.erase(key(i)).notNull()); } - ensure_equals("size after erases", mTable.size(), (size_t)(count - count / 3)); - ensure("consistent after erases", mTable.consistent()); + CHECK(mTable.size() == (size_t)(count - count / 3)); + UNSCOPED_INFO("consistent after erases"); + CHECK(mTable.consistent()); for (U32 i = 1; i <= count; ++i) { Item* found = mTable.find(key(i)); - ensure("erased gone, kept found", (i % 3 == 0) ? found == nullptr : found == items[i - 1]); + CAPTURE(i); + REQUIRE(((i % 3 == 0) ? found == nullptr : found == items[i - 1])); } Table reserved; reserved.reserve(count); for (U32 i = 1; i <= 100; ++i) { - ensure("reserved insert", reserved.insert(key(i), new Item(i))); + CAPTURE(i); + REQUIRE(reserved.insert(key(i), new Item(i))); } - ensure_equals("reserved size", reserved.size(), (size_t)100); - ensure("reserved consistent", reserved.consistent()); + CHECK(reserved.size() == (size_t)100); + CHECK(reserved.consistent()); } // --- find-or-insert ------------------------------------------------------ @@ -414,34 +424,44 @@ namespace tut // A hit hands back the existing item without calling the maker; a miss // calls it once and takes one reference to what it made; a maker that // returns nothing, or an item already in a table, leaves no trace. - template<> template<> - void texturetable_object::test<17>() + TEST_CASE_METHOD(texturetable_data, "ALTextureTable: findOrInsert makes only on a miss and leaves no trace on a refusal", "[ALTextureTable]") { std::vector items = fill(2); U32 made = 0; Item* hit = mTable.findOrInsert(key(2), [&]() { ++made; return new Item(99); }); - ensure("hit returns existing", hit == items[1]); - ensure_equals("maker not called on hit", made, 0u); + CHECK(hit == items[1]); + UNSCOPED_INFO("maker not called on hit"); + CHECK(made == 0u); Item* fresh = mTable.findOrInsert(key(3), [&]() { ++made; return new Item(3); }); - ensure("miss inserts", fresh != nullptr && fresh->mId == 3); - ensure_equals("maker called once", made, 1u); - ensure_equals("one ref from the table", fresh->getNumRefs(), 1); - ensure_equals("position", fresh->getListIndex(), 2); - ensure("found afterwards", mTable.find(key(3)) == fresh); - ensure_equals("size", mTable.size(), (size_t)3); + { + INFO("miss inserts"); + REQUIRE(fresh != nullptr); + CHECK(fresh->mId == 3); + } + UNSCOPED_INFO("maker called once"); + CHECK(made == 1u); + UNSCOPED_INFO("one ref from the table"); + CHECK(fresh->getNumRefs() == 1); + CHECK(fresh->getListIndex() == 2); + CHECK(mTable.find(key(3)) == fresh); + CHECK(mTable.size() == (size_t)3); Item* none = mTable.findOrInsert(key(4), [&]() -> Item* { ++made; return nullptr; }); - ensure("null maker yields null", none == nullptr); - ensure("null maker leaves no key", !mTable.contains(key(4))); - ensure_equals("size after null maker", mTable.size(), (size_t)3); + UNSCOPED_INFO("null maker yields null"); + CHECK(none == nullptr); + UNSCOPED_INFO("null maker leaves no key"); + CHECK_FALSE(mTable.contains(key(4))); + CHECK(mTable.size() == (size_t)3); LLPointer other = new Item(5); mTable.insert(key(5), other); Item* dup = mTable.findOrInsert(key(6), [&]() { return other.get(); }); - ensure("item already in a table refused", dup == nullptr); - ensure("refused key absent", !mTable.contains(key(6))); - ensure("consistent", mTable.consistent()); + UNSCOPED_INFO("item already in a table refused"); + CHECK(dup == nullptr); + UNSCOPED_INFO("refused key absent"); + CHECK_FALSE(mTable.contains(key(6))); + CHECK(mTable.consistent()); } } diff --git a/indra/newview/tests/alwhitebalancesolver_test.cpp b/indra/newview/tests/alwhitebalancesolver_test.cpp index 9b062e967d..7422ad4d66 100644 --- a/indra/newview/tests/alwhitebalancesolver_test.cpp +++ b/indra/newview/tests/alwhitebalancesolver_test.cpp @@ -14,13 +14,14 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include +#include "altestmatchers.h" #include "../alwhitebalancesolver.h" #include -namespace tut +namespace { struct wb_data { @@ -35,33 +36,28 @@ namespace tut } }; - typedef test_group wb_group; - typedef wb_group::object wb_object; - tut::wb_group wbg("ALWhiteBalanceSolver"); - // Neutral in, neutral out. The shader's identity fast path tests the gain // against exactly vec3(1), so anything else here would leave the default // settings paying for a colour transform that does nothing. - template<> template<> - void wb_object::test<1>() + TEST_CASE_METHOD(wb_data, "ALWhiteBalanceSolver: neutral in gives a gain of one", "[ALWhiteBalanceSolver]") { const LLVector3 g = ALWhiteBalanceSolver::gain(0.f, 0.f); - ensure_approximately_equals("neutral red", g.mV[VX], 1.f, 6); - ensure_approximately_equals("neutral green", g.mV[VY], 1.f, 6); - ensure_approximately_equals("neutral blue", g.mV[VZ], 1.f, 6); + CHECK_THAT(g.mV[VX], ll_test::WithinBits(1.f, 6)); + CHECK_THAT(g.mV[VY], ll_test::WithinBits(1.f, 6)); + CHECK_THAT(g.mV[VZ], ll_test::WithinBits(1.f, 6)); } // Green is pinned across the whole range, which is what makes temperature // a colour control rather than an exposure one. - template<> template<> - void wb_object::test<2>() + TEST_CASE_METHOD(wb_data, "ALWhiteBalanceSolver: the green gain stays pinned at one across the whole range", "[ALWhiteBalanceSolver]") { for (S32 i = -5; i <= 5; ++i) { for (S32 j = -2; j <= 2; ++j) { const LLVector3 g = ALWhiteBalanceSolver::gain(i * 1000.f, j * 0.5f); - ensure_approximately_equals("green stays pinned", g.mV[VY], 1.f, 6); + CAPTURE(i, j); + REQUIRE_THAT(g.mV[VY], ll_test::WithinBits(1.f, 6)); } } } @@ -69,19 +65,19 @@ namespace tut // The direction of the temperature control. A negative offset asks for a // warmer scene, which the renderer delivers by pushing red up relative to // blue -- if this ever inverts, every tooltip in the Basic panel is wrong. - template<> template<> - void wb_object::test<3>() + TEST_CASE_METHOD(wb_data, "ALWhiteBalanceSolver: a negative temperature offset warms and a positive one cools", "[ALWhiteBalanceSolver]") { const LLVector3 warm = ALWhiteBalanceSolver::gain(-2500.f, 0.f); const LLVector3 cool = ALWhiteBalanceSolver::gain( 2500.f, 0.f); - ensure("warm lifts red above blue", warm.mV[VX] > warm.mV[VZ]); - ensure("cool lifts blue above red", cool.mV[VZ] > cool.mV[VX]); + UNSCOPED_INFO("warm lifts red above blue"); + CHECK(warm.mV[VX] > warm.mV[VZ]); + UNSCOPED_INFO("cool lifts blue above red"); + CHECK(cool.mV[VZ] > cool.mV[VX]); } // The round trip the eyedropper depends on: every usable pair is recovered // from the gain it produces. - template<> template<> - void wb_object::test<4>() + TEST_CASE_METHOD(wb_data, "ALWhiteBalanceSolver: solve recovers every usable pair from the gain it produces", "[ALWhiteBalanceSolver]") { S32 tested = 0; for (S32 i = -4; i <= 4; ++i) @@ -90,10 +86,12 @@ namespace tut { const F32 cct = i * 1200.f; const F32 duv = j * 0.25f; + CAPTURE(cct, duv); // Out of gamut is out of scope: down there the blue gain is // negative, so every candidate looks alike to the solver and - // no inverse exists to test. Test <9> covers that region. + // no inverse exists to test. The out-of-gamut case below + // covers that region. if (!ALWhiteBalanceSolver::isUsable(cct, duv)) { continue; @@ -110,19 +108,21 @@ namespace tut // or unachievable where it does not. What has to round-trip is // the colour the renderer will actually apply. const LLVector3 back = ALWhiteBalanceSolver::gain(got.mCCTOffset, got.mDuv); - ensure("gain round-trips", gainDistance(want, back) < 1e-3f); - ensure("and the solver knows it did", got.mResidual < 1e-3f); + UNSCOPED_INFO("gain round-trips"); + REQUIRE(gainDistance(want, back) < 1e-3f); + UNSCOPED_INFO("and the solver knows it did"); + REQUIRE(got.mResidual < 1e-3f); } } // Guards against the skip above quietly emptying the sweep. - ensure("the sweep covered most of the range", tested > 60); + UNSCOPED_INFO("the sweep covered most of the range"); + CHECK(tested > 60); } // A solution never escapes the sliders' range, whatever it is asked for. // Out here the answer is a best effort against the edge of the box, and // the residual is how the caller finds out. - template<> template<> - void wb_object::test<5>() + TEST_CASE_METHOD(wb_data, "ALWhiteBalanceSolver: a solution stays in the sliders' range whatever it is asked for", "[ALWhiteBalanceSolver]") { // Both free channels pulled well below green: "make it much greener // than any light source is". Temperature trades red against blue and @@ -132,44 +132,53 @@ namespace tut const LLVector3 absurd(0.3f, 1.f, 0.3f); const ALWhiteBalanceSolver::Result got = ALWhiteBalanceSolver::solve(absurd); - ensure("cct stays in range", - got.mCCTOffset >= ALWhiteBalanceSolver::CCT_MIN && - got.mCCTOffset <= ALWhiteBalanceSolver::CCT_MAX); - ensure("duv stays in range", - got.mDuv >= ALWhiteBalanceSolver::DUV_MIN && - got.mDuv <= ALWhiteBalanceSolver::DUV_MAX); - ensure("and it reports that it could not get there", got.mResidual > 0.1f); + { + INFO("cct stays in range"); + CHECK(got.mCCTOffset >= ALWhiteBalanceSolver::CCT_MIN); + CHECK(got.mCCTOffset <= ALWhiteBalanceSolver::CCT_MAX); + } + { + INFO("duv stays in range"); + CHECK(got.mDuv >= ALWhiteBalanceSolver::DUV_MIN); + CHECK(got.mDuv <= ALWhiteBalanceSolver::DUV_MAX); + } + UNSCOPED_INFO("and it reports that it could not get there"); + CHECK(got.mResidual > 0.1f); } // The gain that neutralises a colour, and the sign of what it does. - template<> template<> - void wb_object::test<6>() + TEST_CASE_METHOD(wb_data, "ALWhiteBalanceSolver: neutralisingGain pins green and scales red and blue to meet it", "[ALWhiteBalanceSolver]") { const LLVector3 g = ALWhiteBalanceSolver::neutralisingGain(LLColor3(0.4f, 0.5f, 0.8f)); - ensure_approximately_equals("green pinned", g.mV[VY], 1.f, 6); - ensure_approximately_equals("red is lifted to meet green", g.mV[VX], 1.25f, 5); - ensure_approximately_equals("blue is pulled down to it", g.mV[VZ], 0.625f, 5); + CHECK_THAT(g.mV[VY], ll_test::WithinBits(1.f, 6)); + UNSCOPED_INFO("red is lifted to meet green"); + CHECK_THAT(g.mV[VX], ll_test::WithinBits(1.25f, 5)); + UNSCOPED_INFO("blue is pulled down to it"); + CHECK_THAT(g.mV[VZ], ll_test::WithinBits(0.625f, 5)); // Applying it does what it says. const LLColor3 sample(0.4f, 0.5f, 0.8f); - ensure_approximately_equals("corrected red equals green", sample.mV[0] * g.mV[VX], sample.mV[1], 5); - ensure_approximately_equals("corrected blue equals green", sample.mV[2] * g.mV[VZ], sample.mV[1], 5); + UNSCOPED_INFO("corrected red equals green"); + CHECK_THAT(sample.mV[0] * g.mV[VX], ll_test::WithinBits(sample.mV[1], 5)); + UNSCOPED_INFO("corrected blue equals green"); + CHECK_THAT(sample.mV[2] * g.mV[VZ], ll_test::WithinBits(sample.mV[1], 5)); // Scale-invariant: the eyedropper cares about the colour of a sample, // never how brightly it was lit. const LLVector3 dim = ALWhiteBalanceSolver::neutralisingGain(LLColor3(0.04f, 0.05f, 0.08f)); - ensure("brightness does not change the answer", gainDistance(g, dim) < 1e-4f); + UNSCOPED_INFO("brightness does not change the answer"); + CHECK(gainDistance(g, dim) < 1e-4f); } // The whole eyedropper, end to end: take a neutral surface, light it the // way some (cct, duv) pair would, and the solver should recover the pair // that undoes it. - template<> template<> - void wb_object::test<7>() + TEST_CASE_METHOD(wb_data, "ALWhiteBalanceSolver: solveForColor recovers the light that tinted a grey surface", "[ALWhiteBalanceSolver]") { for (S32 i = -3; i <= 3; ++i) { const F32 cct = i * 1500.f; + CAPTURE(cct); // A grey surface seen through the inverse of the correction: this // is what the scene buffer holds when the light is that colour. @@ -179,26 +188,28 @@ namespace tut const ALWhiteBalanceSolver::Result got = ALWhiteBalanceSolver::solveForColor(lit); const LLVector3 back = ALWhiteBalanceSolver::gain(got.mCCTOffset, got.mDuv); - ensure("recovers the light's own correction", - gainDistance(correction, back) < 1e-3f); + UNSCOPED_INFO("recovers the light's own correction"); + REQUIRE(gainDistance(correction, back) < 1e-3f); // And the sample really does come out neutral. const LLColor3 fixed(lit.mV[0] * back.mV[VX], lit.mV[1], lit.mV[2] * back.mV[VZ]); - ensure_approximately_equals("neutralised red", fixed.mV[0], fixed.mV[1], 4); - ensure_approximately_equals("neutralised blue", fixed.mV[2], fixed.mV[1], 4); + UNSCOPED_INFO("neutralised red"); + REQUIRE_THAT(fixed.mV[0], ll_test::WithinBits(fixed.mV[1], 4)); + UNSCOPED_INFO("neutralised blue"); + REQUIRE_THAT(fixed.mV[2], ll_test::WithinBits(fixed.mV[1], 4)); } } // An already-neutral sample asks for nothing, so clicking a grey wall in // a correctly balanced scene must not nudge the sliders off zero. - template<> template<> - void wb_object::test<8>() + TEST_CASE_METHOD(wb_data, "ALWhiteBalanceSolver: an already-neutral sample asks for no correction", "[ALWhiteBalanceSolver]") { const ALWhiteBalanceSolver::Result got = ALWhiteBalanceSolver::solveForColor(LLColor3(0.5f, 0.5f, 0.5f)); - ensure("cct stays put", std::fabs(got.mCCTOffset) < 1.f); - ensure("duv stays put", std::fabs(got.mDuv) < 1e-3f); - ensure("exactly reachable", got.mResidual < 1e-4f); + CHECK(std::fabs(got.mCCTOffset) < 1.f); + CHECK(std::fabs(got.mDuv) < 1e-3f); + UNSCOPED_INFO("exactly reachable"); + CHECK(got.mResidual < 1e-4f); } // The warm end of the Temperature slider leaves the sRGB gamut: below @@ -207,23 +218,30 @@ namespace tut // sign. That is the renderer's existing behaviour and this fixes none of // it; what is pinned here is that the solver stays out of the region, so // the eyedropper can never hand a user a balance that does that. - template<> template<> - void wb_object::test<9>() + TEST_CASE_METHOD(wb_data, "ALWhiteBalanceSolver: the solver stays out of the out-of-gamut warm end", "[ALWhiteBalanceSolver]") { - ensure("neutral is usable", ALWhiteBalanceSolver::isUsable(0.f, 0.f)); - ensure("the cool end is usable", ALWhiteBalanceSolver::isUsable(5000.f, 0.f)); - ensure("the warm extreme is not", !ALWhiteBalanceSolver::isUsable(-5000.f, 0.f)); + UNSCOPED_INFO("neutral is usable"); + CHECK(ALWhiteBalanceSolver::isUsable(0.f, 0.f)); + UNSCOPED_INFO("the cool end is usable"); + CHECK(ALWhiteBalanceSolver::isUsable(5000.f, 0.f)); + UNSCOPED_INFO("the warm extreme is not"); + CHECK_FALSE(ALWhiteBalanceSolver::isUsable(-5000.f, 0.f)); // Tint moves the boundary: pushing green costs gamut at the warm end. - ensure("-4000 is usable at neutral tint", ALWhiteBalanceSolver::isUsable(-4000.f, 0.f)); - ensure("but not at full green tint", !ALWhiteBalanceSolver::isUsable(-4000.f, 1.f)); + UNSCOPED_INFO("-4000 is usable at neutral tint"); + CHECK(ALWhiteBalanceSolver::isUsable(-4000.f, 0.f)); + UNSCOPED_INFO("but not at full green tint"); + CHECK_FALSE(ALWhiteBalanceSolver::isUsable(-4000.f, 1.f)); // Ask for something only the out-of-gamut region could match, and the // answer must still be a balance that works. const LLVector3 want = ALWhiteBalanceSolver::gain(-5000.f, 0.f); const ALWhiteBalanceSolver::Result got = ALWhiteBalanceSolver::solve(want); - ensure("the solution is usable", ALWhiteBalanceSolver::isUsable(got.mCCTOffset, got.mDuv)); + UNSCOPED_INFO("the solution is usable"); + CHECK(ALWhiteBalanceSolver::isUsable(got.mCCTOffset, got.mDuv)); const LLVector3 back = ALWhiteBalanceSolver::gain(got.mCCTOffset, got.mDuv); - ensure("its red gain is positive", back.mV[VX] > 0.f); - ensure("and so is its blue", back.mV[VZ] > 0.f); + UNSCOPED_INFO("its red gain is positive"); + CHECK(back.mV[VX] > 0.f); + UNSCOPED_INFO("and so is its blue"); + CHECK(back.mV[VZ] > 0.f); } } diff --git a/indra/newview/tests/cppfeatures_test.cpp b/indra/newview/tests/cppfeatures_test.cpp index 82d697f332..a256a6dfb7 100644 --- a/indra/newview/tests/cppfeatures_test.cpp +++ b/indra/newview/tests/cppfeatures_test.cpp @@ -29,29 +29,23 @@ // Tests related to newer C++ features, for verifying support across compilers and platforms #include "linden_common.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { -struct cpp_features_test {}; -typedef test_group cpp_features_test_t; -typedef cpp_features_test_t::object cpp_features_test_object_t; -tut::cpp_features_test_t tut_cpp_features_test("LLCPPFeatures"); - // bracket initializers // Can initialize containers or values using curly brackets -template<> template<> -void cpp_features_test_object_t::test<1>() +TEST_CASE("LLCPPFeatures: bracket initializers set values and containers", "[LLCPPFeatures]") { S32 explicit_val{3}; - ensure(explicit_val==3); + CHECK(explicit_val==3); S32 default_val{}; - ensure(default_val==0); + CHECK(default_val==0); std::vector fibs{1,1,2,3,5}; - ensure(fibs[4]==5); + CHECK(fibs[4]==5); } // auto @@ -59,20 +53,19 @@ void cpp_features_test_object_t::test<1>() // https://en.cppreference.com/w/cpp/language/auto // // Can use auto in place of a more complex type specification, if the compiler can infer the type -template<> template<> -void cpp_features_test_object_t::test<2>() +TEST_CASE("LLCPPFeatures: auto deduces an element and an iterator type", "[LLCPPFeatures]") { std::vector numbers{3,6,9}; // auto element auto& aval = numbers[1]; - ensure("auto element", aval==6); + CHECK(aval==6); // auto iterator (non-const) auto it = numbers.rbegin(); *it += 1; S32 val = *it; - ensure("auto iterator", val==10); + CHECK(val==10); } // range for @@ -80,8 +73,7 @@ void cpp_features_test_object_t::test<2>() // https://en.cppreference.com/w/cpp/language/range-for // // Can iterate over containers without explicit iterator -template<> template<> -void cpp_features_test_object_t::test<3>() +TEST_CASE("LLCPPFeatures: range for walks a container or a C array", "[LLCPPFeatures]") { // Traditional iterator for with container @@ -95,7 +87,7 @@ void cpp_features_test_object_t::test<3>() auto& n = *it; n *= 2; } - ensure("iterator for vector", numbers[2]==18); + CHECK(numbers[2]==18); // Range for with container // @@ -107,7 +99,7 @@ void cpp_features_test_object_t::test<3>() { n *= 2; } - ensure("range for vector", numbersb[2]==18); + CHECK(numbersb[2]==18); // Range for over a C-style array. // @@ -119,7 +111,7 @@ void cpp_features_test_object_t::test<3>() { sum += v; } - ensure("for C-array", sum==31); + CHECK(sum==31); } // override specifier @@ -134,8 +126,6 @@ void cpp_features_test_object_t::test<3>() // * Some compilers require that any class using override must use it consistently for all functions. // This makes switching a class to use override a lot more work. -namespace -{ class Foo { @@ -153,13 +143,11 @@ class Bar: public Foo // bool is_happx() override { return true; } }; -} // anonymous namespace -template<> template<> -void cpp_features_test_object_t::test<4>() +TEST_CASE("LLCPPFeatures: an override specifier marks a virtual override", "[LLCPPFeatures]") { Bar b; - ensure("override", b.is_happy()); + CHECK(b.is_happy()); } // final @@ -168,8 +156,6 @@ void cpp_features_test_object_t::test<4>() // virtual function cannot be overridden in a derived class or that a // class cannot be inherited from." -namespace -{ class Vehicle { @@ -190,13 +176,11 @@ class Bicycle: public WheeledVehicle // virtual bool has_wheels() override const { return true; } }; -} // anonymous namespace -template<> template<> -void cpp_features_test_object_t::test<5>() +TEST_CASE("LLCPPFeatures: a class derives from one with a final override", "[LLCPPFeatures]") { Bicycle bi; - ensure("final", bi.has_wheels()); + CHECK(bi.has_wheels()); } // deleted function declaration @@ -213,8 +197,6 @@ void cpp_features_test_object_t::test<5>() // * Makes the intention clear // * Creates an error sooner, at compile time -namespace -{ class DoNotCopy { @@ -223,10 +205,8 @@ class DoNotCopy DoNotCopy(const DoNotCopy& ref) = delete; }; -} // anonymous namespace -template<> template<> -void cpp_features_test_object_t::test<6>() +TEST_CASE("LLCPPFeatures: a deleted copy constructor leaves default construction", "[LLCPPFeatures]") { DoNotCopy nc; // OK, default constructor //DoNotCopy nc2(nc); // No, can't copy @@ -241,8 +221,6 @@ void cpp_features_test_object_t::test<6>() // and are happy with the default implementation the compiler will make (memberwise copy). // Now you can explicitly declare that too. // Usage: I guess it makes the intent clearer, but otherwise not obviously useful. -namespace -{ class DefaultCopyOK { @@ -254,17 +232,15 @@ class DefaultCopyOK S32 mVal; }; -} // anonymous namespace -template<> template<> -void cpp_features_test_object_t::test<7>() +TEST_CASE("LLCPPFeatures: a defaulted copy constructor copies the members", "[LLCPPFeatures]") { DefaultCopyOK d; // OK DefaultCopyOK d2(d); // OK DefaultCopyOK d3 = d; // OK - ensure("default copy d", d.val()==123); - ensure("default copy d2", d.val()==d2.val()); - ensure("default copy d3", d.val()==d3.val()); + CHECK(d.val()==123); + CHECK(d.val()==d2.val()); + CHECK(d.val()==d3.val()); } // initialize class members inline @@ -276,8 +252,6 @@ void cpp_features_test_object_t::test<7>() // It is preferred to skip creating a constructor if all the work can be done by inline initialization: // http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines.html#c45-dont-define-a-default-constructor-that-only-initializes-data-members-use-in-class-member-initializers-instead // -namespace -{ class InitInline { @@ -299,17 +273,15 @@ class InitInlineWithConstructor S32 mBar = 20; }; -} // anonymous namespace -template<> template<> -void cpp_features_test_object_t::test<8>() +TEST_CASE("LLCPPFeatures: inline member initializers give defaults a constructor overrides", "[LLCPPFeatures]") { InitInline ii; - ensure("init member inline 1", ii.mFoo==10); + CHECK(ii.mFoo==10); InitInlineWithConstructor iici; - ensure("init member inline 2", iici.mFoo==10); - ensure("init member inline 3", iici.mBar==25); + CHECK(iici.mFoo==10); + CHECK(iici.mBar==25); } // constexpr @@ -317,8 +289,6 @@ void cpp_features_test_object_t::test<8>() // https://en.cppreference.com/w/cpp/language/constexpr // // Various things can be computed at compile time, and flagged as constexpr. -namespace -{ constexpr S32 compute2() { return 2; } @@ -334,17 +304,15 @@ constexpr S32 ce_factorial(S32 n) } } -} // anonymous namespace -template<> template<> -void cpp_features_test_object_t::test<9>() +TEST_CASE("LLCPPFeatures: constexpr functions compute values", "[LLCPPFeatures]") { S32 val = compute2(); - ensure("constexpr 1", val==2); + CHECK(val==2); // Compile-time factorial. You used to need complex templates to do something this useless. S32 fac5 = ce_factorial(5); - ensure("constexpr 2", fac5==120); + CHECK(fac5==120); } // static assert @@ -358,8 +326,7 @@ void cpp_features_test_object_t::test<9>() // // Currently only the 2-parameter form works on windows. The 1-parameter form needs a flag we don't set. -template<> template<> -void cpp_features_test_object_t::test<10>() +TEST_CASE("LLCPPFeatures: static_assert checks a constexpr at compile time", "[LLCPPFeatures]") { // static_assert(ce_factorial(6)==720); No, needs a flag we don't currently set. static_assert(ce_factorial(6)==720, "bad factorial"); // OK @@ -376,11 +343,10 @@ void cpp_features_test_object_t::test<10>() template using stringmap = std::map; -template<> template<> -void cpp_features_test_object_t::test<11>() +TEST_CASE("LLCPPFeatures: a template type alias names a map type", "[LLCPPFeatures]") { stringmap name_counts{ {"alice", 3}, {"bob", 2} }; - ensure("type alias", name_counts["bob"]==2); + CHECK(name_counts["bob"]==2); } // Other possibilities: @@ -413,4 +379,4 @@ void cpp_features_test_object_t::test<11>() // string_view -} // namespace tut +} // anonymous namespace diff --git a/indra/newview/tests/llagentaccess_test.cpp b/indra/newview/tests/llagentaccess_test.cpp index 747157a808..4fd2c2ca93 100644 --- a/indra/newview/tests/llagentaccess_test.cpp +++ b/indra/newview/tests/llagentaccess_test.cpp @@ -25,7 +25,7 @@ */ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../llagentaccess.h" @@ -34,18 +34,9 @@ #include -namespace tut +namespace { - struct agentaccess - { - }; - - typedef test_group agentaccess_t; - typedef agentaccess_t::object agentaccess_object_t; - tut::agentaccess_t tut_agentaccess("LLAgentAccess"); - - template<> template<> - void agentaccess_object_t::test<1>() + TEST_CASE("LLAgentAccess: the prefers queries follow the PreferredMaturity setting", "[LLAgentAccess]") { LLControlGroup cgr("llagentaccess"); cgr.declareU32("PreferredMaturity", SIM_ACCESS_PG, "declared_for_test", LLControlVariable::PERSIST_NO); @@ -53,28 +44,27 @@ namespace tut cgr.setU32("PreferredMaturity", SIM_ACCESS_PG); #ifndef HACKED_GODLIKE_VIEWER - ensure("1 prefersPG", aa.prefersPG()); - ensure("1 prefersMature", !aa.prefersMature()); - ensure("1 prefersAdult", !aa.prefersAdult()); + CHECK(aa.prefersPG()); + CHECK_FALSE(aa.prefersMature()); + CHECK_FALSE(aa.prefersAdult()); #endif // HACKED_GODLIKE_VIEWER cgr.setU32("PreferredMaturity", SIM_ACCESS_MATURE); #ifndef HACKED_GODLIKE_VIEWER - ensure("2 prefersPG", !aa.prefersPG()); - ensure("2 prefersMature", aa.prefersMature()); - ensure("2 prefersAdult", !aa.prefersAdult()); + CHECK_FALSE(aa.prefersPG()); + CHECK(aa.prefersMature()); + CHECK_FALSE(aa.prefersAdult()); #endif // HACKED_GODLIKE_VIEWER cgr.setU32("PreferredMaturity", SIM_ACCESS_ADULT); #ifndef HACKED_GODLIKE_VIEWER - ensure("3 prefersPG", !aa.prefersPG()); - ensure("3 prefersMature", aa.prefersMature()); - ensure("3 prefersAdult", aa.prefersAdult()); + CHECK_FALSE(aa.prefersPG()); + CHECK(aa.prefersMature()); + CHECK(aa.prefersAdult()); #endif // HACKED_GODLIKE_VIEWER } - template<> template<> - void agentaccess_object_t::test<2>() + TEST_CASE("LLAgentAccess: maturity starts PG and setMaturity takes the P M and A letters", "[LLAgentAccess]") { LLControlGroup cgr("llagentaccess"); cgr.declareU32("PreferredMaturity", SIM_ACCESS_PG, "declared_for_test", LLControlVariable::PERSIST_NO); @@ -82,146 +72,175 @@ namespace tut // make sure default is PG #ifndef HACKED_GODLIKE_VIEWER - ensure("1 isTeen", aa.isTeen()); - ensure("1 isMature", !aa.isMature()); - ensure("1 isAdult", !aa.isAdult()); + CHECK(aa.isTeen()); + CHECK_FALSE(aa.isMature()); + CHECK_FALSE(aa.isAdult()); #endif // HACKED_GODLIKE_VIEWER // check the conversion routine #ifndef HACKED_GODLIKE_VIEWER - ensure_equals("1 conversion", SIM_ACCESS_PG, aa.convertTextToMaturity('P')); - ensure_equals("2 conversion", SIM_ACCESS_MATURE, aa.convertTextToMaturity('M')); - ensure_equals("3 conversion", SIM_ACCESS_ADULT, aa.convertTextToMaturity('A')); - ensure_equals("4 conversion", SIM_ACCESS_MIN, aa.convertTextToMaturity('Q')); + CHECK(SIM_ACCESS_PG == aa.convertTextToMaturity('P')); + CHECK(SIM_ACCESS_MATURE == aa.convertTextToMaturity('M')); + CHECK(SIM_ACCESS_ADULT == aa.convertTextToMaturity('A')); + CHECK(SIM_ACCESS_MIN == aa.convertTextToMaturity('Q')); #endif // HACKED_GODLIKE_VIEWER // now try the other method of setting it - PG aa.setMaturity('P'); - ensure("2 isTeen", aa.isTeen()); + CHECK(aa.isTeen()); #ifndef HACKED_GODLIKE_VIEWER - ensure("2 isMature", !aa.isMature()); - ensure("2 isAdult", !aa.isAdult()); + CHECK_FALSE(aa.isMature()); + CHECK_FALSE(aa.isAdult()); #endif // HACKED_GODLIKE_VIEWER // Mature aa.setMaturity('M'); #ifndef HACKED_GODLIKE_VIEWER - ensure("3 isTeen", !aa.isTeen()); - ensure("3 isMature", aa.isMature()); - ensure("3 isAdult", !aa.isAdult()); + CHECK_FALSE(aa.isTeen()); + CHECK(aa.isMature()); + CHECK_FALSE(aa.isAdult()); #endif // HACKED_GODLIKE_VIEWER // Adult aa.setMaturity('A'); #ifndef HACKED_GODLIKE_VIEWER - ensure("4 isTeen", !aa.isTeen()); - ensure("4 isMature", aa.isMature()); - ensure("4 isAdult", aa.isAdult()); + CHECK_FALSE(aa.isTeen()); + CHECK(aa.isMature()); + CHECK(aa.isAdult()); #endif // HACKED_GODLIKE_VIEWER } - template<> template<> - void agentaccess_object_t::test<3>() + TEST_CASE("LLAgentAccess: isGodlike follows the god level and the admin override", "[LLAgentAccess]") { LLControlGroup cgr("llagentaccess"); cgr.declareU32("PreferredMaturity", SIM_ACCESS_PG, "declared_for_test", LLControlVariable::PERSIST_NO); LLAgentAccess aa(cgr); #ifndef HACKED_GODLIKE_VIEWER - ensure("starts normal", !aa.isGodlike()); + UNSCOPED_INFO("starts normal"); + CHECK_FALSE(aa.isGodlike()); #endif // HACKED_GODLIKE_VIEWER aa.setGodLevel(GOD_NOT); #ifndef HACKED_GODLIKE_VIEWER - ensure("stays normal", !aa.isGodlike()); + UNSCOPED_INFO("stays normal"); + CHECK_FALSE(aa.isGodlike()); #endif // HACKED_GODLIKE_VIEWER aa.setGodLevel(GOD_FULL); #ifndef HACKED_GODLIKE_VIEWER - ensure("sets full", aa.isGodlike()); + UNSCOPED_INFO("sets full"); + CHECK(aa.isGodlike()); #endif // HACKED_GODLIKE_VIEWER aa.setGodLevel(GOD_NOT); #ifndef HACKED_GODLIKE_VIEWER - ensure("resets normal", !aa.isGodlike()); + UNSCOPED_INFO("resets normal"); + CHECK_FALSE(aa.isGodlike()); #endif // HACKED_GODLIKE_VIEWER aa.setAdminOverride(true); #ifndef HACKED_GODLIKE_VIEWER - ensure("admin true", aa.getAdminOverride()); - ensure("overrides 1", aa.isGodlike()); + CHECK(aa.getAdminOverride()); + UNSCOPED_INFO("the admin override alone"); + CHECK(aa.isGodlike()); #endif // HACKED_GODLIKE_VIEWER aa.setGodLevel(GOD_FULL); #ifndef HACKED_GODLIKE_VIEWER - ensure("overrides 2", aa.isGodlike()); + UNSCOPED_INFO("the admin override and full god level"); + CHECK(aa.isGodlike()); #endif // HACKED_GODLIKE_VIEWER aa.setAdminOverride(false); #ifndef HACKED_GODLIKE_VIEWER - ensure("admin false", !aa.getAdminOverride()); - ensure("overrides 3", aa.isGodlike()); + CHECK_FALSE(aa.getAdminOverride()); + UNSCOPED_INFO("full god level alone"); + CHECK(aa.isGodlike()); #endif // HACKED_GODLIKE_VIEWER } - template<> template<> - void agentaccess_object_t::test<4>() + TEST_CASE("LLAgentAccess: access needs both rating and preference unless god or admin", "[LLAgentAccess]") { LLControlGroup cgr("llagentaccess"); cgr.declareU32("PreferredMaturity", SIM_ACCESS_PG, "declared_for_test", LLControlVariable::PERSIST_NO); LLAgentAccess aa(cgr); #ifndef HACKED_GODLIKE_VIEWER - ensure("1 pg to start", aa.wantsPGOnly()); - ensure("2 pg to start", !aa.canAccessMature()); - ensure("3 pg to start", !aa.canAccessAdult()); + { + INFO("pg to start"); + CHECK(aa.wantsPGOnly()); + CHECK_FALSE(aa.canAccessMature()); + CHECK_FALSE(aa.canAccessAdult()); + } #endif // HACKED_GODLIKE_VIEWER aa.setGodLevel(GOD_FULL); #ifndef HACKED_GODLIKE_VIEWER - ensure("1 full god", !aa.wantsPGOnly()); - ensure("2 full god", aa.canAccessMature()); - ensure("3 full god", aa.canAccessAdult()); + { + INFO("full god"); + CHECK_FALSE(aa.wantsPGOnly()); + CHECK(aa.canAccessMature()); + CHECK(aa.canAccessAdult()); + } #endif // HACKED_GODLIKE_VIEWER aa.setGodLevel(GOD_NOT); aa.setAdminOverride(true); #ifndef HACKED_GODLIKE_VIEWER - ensure("1 admin mode", !aa.wantsPGOnly()); - ensure("2 admin mode", aa.canAccessMature()); - ensure("3 admin mode", aa.canAccessAdult()); + { + INFO("admin mode"); + CHECK_FALSE(aa.wantsPGOnly()); + CHECK(aa.canAccessMature()); + CHECK(aa.canAccessAdult()); + } #endif // HACKED_GODLIKE_VIEWER aa.setAdminOverride(false); aa.setMaturity('M'); // preferred is still pg by default #ifndef HACKED_GODLIKE_VIEWER - ensure("1 mature pref pg", aa.wantsPGOnly()); - ensure("2 mature pref pg", !aa.canAccessMature()); - ensure("3 mature pref pg", !aa.canAccessAdult()); + { + INFO("mature pref pg"); + CHECK(aa.wantsPGOnly()); + CHECK_FALSE(aa.canAccessMature()); + CHECK_FALSE(aa.canAccessAdult()); + } #endif // HACKED_GODLIKE_VIEWER cgr.setU32("PreferredMaturity", SIM_ACCESS_MATURE); #ifndef HACKED_GODLIKE_VIEWER - ensure("1 mature", !aa.wantsPGOnly()); - ensure("2 mature", aa.canAccessMature()); - ensure("3 mature", !aa.canAccessAdult()); + { + INFO("mature"); + CHECK_FALSE(aa.wantsPGOnly()); + CHECK(aa.canAccessMature()); + CHECK_FALSE(aa.canAccessAdult()); + } #endif // HACKED_GODLIKE_VIEWER cgr.setU32("PreferredMaturity", SIM_ACCESS_PG); #ifndef HACKED_GODLIKE_VIEWER - ensure("1 mature pref pg", aa.wantsPGOnly()); - ensure("2 mature pref pg", !aa.canAccessMature()); - ensure("3 mature pref pg", !aa.canAccessAdult()); + { + INFO("mature pref pg"); + CHECK(aa.wantsPGOnly()); + CHECK_FALSE(aa.canAccessMature()); + CHECK_FALSE(aa.canAccessAdult()); + } #endif // HACKED_GODLIKE_VIEWER aa.setMaturity('A'); #ifndef HACKED_GODLIKE_VIEWER - ensure("1 adult pref pg", aa.wantsPGOnly()); - ensure("2 adult pref pg", !aa.canAccessMature()); - ensure("3 adult pref pg", !aa.canAccessAdult()); + { + INFO("adult pref pg"); + CHECK(aa.wantsPGOnly()); + CHECK_FALSE(aa.canAccessMature()); + CHECK_FALSE(aa.canAccessAdult()); + } #endif // HACKED_GODLIKE_VIEWER cgr.setU32("PreferredMaturity", SIM_ACCESS_ADULT); #ifndef HACKED_GODLIKE_VIEWER - ensure("1 adult", !aa.wantsPGOnly()); - ensure("2 adult", aa.canAccessMature()); - ensure("3 adult", aa.canAccessAdult()); + { + INFO("adult"); + CHECK_FALSE(aa.wantsPGOnly()); + CHECK(aa.canAccessMature()); + CHECK(aa.canAccessAdult()); + } #endif // HACKED_GODLIKE_VIEWER // make sure that even if pref is high, if access is low we block access @@ -229,15 +248,17 @@ namespace tut cgr.setU32("PreferredMaturity", SIM_ACCESS_ADULT); aa.setMaturity('P'); #ifndef HACKED_GODLIKE_VIEWER - ensure("1 pref adult, actual pg", aa.wantsPGOnly()); - ensure("2 pref adult, actual pg", !aa.canAccessMature()); - ensure("3 pref adult, actual pg", !aa.canAccessAdult()); + { + INFO("pref adult, actual pg"); + CHECK(aa.wantsPGOnly()); + CHECK_FALSE(aa.canAccessMature()); + CHECK_FALSE(aa.canAccessAdult()); + } #endif // HACKED_GODLIKE_VIEWER } - template<> template<> - void agentaccess_object_t::test<5>() + TEST_CASE("LLAgentAccess: setMaturity lowers a higher preferred maturity to match", "[LLAgentAccess]") { LLControlGroup cgr("llagentaccess"); cgr.declareU32("PreferredMaturity", SIM_ACCESS_PG, "declared_for_test", LLControlVariable::PERSIST_NO); @@ -246,12 +267,12 @@ namespace tut cgr.setU32("PreferredMaturity", SIM_ACCESS_ADULT); aa.setMaturity('M'); #ifndef HACKED_GODLIKE_VIEWER - ensure("1 preferred maturity pegged to M when maturity is M", cgr.getU32("PreferredMaturity") == SIM_ACCESS_MATURE); + CHECK(cgr.getU32("PreferredMaturity") == SIM_ACCESS_MATURE); #endif // HACKED_GODLIKE_VIEWER aa.setMaturity('P'); #ifndef HACKED_GODLIKE_VIEWER - ensure("1 preferred maturity pegged to P when maturity is P", cgr.getU32("PreferredMaturity") == SIM_ACCESS_PG); + CHECK(cgr.getU32("PreferredMaturity") == SIM_ACCESS_PG); #endif // HACKED_GODLIKE_VIEWER } } diff --git a/indra/newview/tests/lldateutil_test.cpp b/indra/newview/tests/lldateutil_test.cpp index cbe8b250a7..2ac2da00ae 100644 --- a/indra/newview/tests/lldateutil_test.cpp +++ b/indra/newview/tests/lldateutil_test.cpp @@ -25,7 +25,7 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../lldateutil.h" @@ -75,7 +75,7 @@ std::string LLUI::getLanguage() return "en"; } -namespace tut +namespace { struct dateutil { @@ -109,87 +109,57 @@ namespace tut LLDate mNow; }; - typedef test_group dateutil_t; - typedef dateutil_t::object dateutil_object_t; - tut::dateutil_t tut_dateutil("LLDateUtil"); - - template<> template<> - void dateutil_object_t::test<1>() + TEST_CASE_METHOD(dateutil, "LLDateUtil: an age of a year or more reads in years and whole months", "[LLDateUtil]") { - set_test_name("Years"); - ensure_equals("years + months", - LLDateUtil::ageFromDate("10/30/2007", mNow), - "2 years 2 months old" ); - ensure_equals("years", - LLDateUtil::ageFromDate("12/31/2007", mNow), - "2 years old" ); - ensure_equals("years", - LLDateUtil::ageFromDate("1/1/2008", mNow), - "1 year 11 months old" ); - ensure_equals("single year + one month", - LLDateUtil::ageFromDate("11/30/2008", mNow), - "1 year 1 month old" ); - ensure_equals("single year + a bit", - LLDateUtil::ageFromDate("12/12/2008", mNow), - "1 year old" ); - ensure_equals("single year", - LLDateUtil::ageFromDate("12/31/2008", mNow), - "1 year old" ); + CHECK(LLDateUtil::ageFromDate("10/30/2007", mNow) == + "2 years 2 months old"); + CHECK(LLDateUtil::ageFromDate("12/31/2007", mNow) == + "2 years old"); + CHECK(LLDateUtil::ageFromDate("1/1/2008", mNow) == + "1 year 11 months old"); + CHECK(LLDateUtil::ageFromDate("11/30/2008", mNow) == + "1 year 1 month old"); + CHECK(LLDateUtil::ageFromDate("12/12/2008", mNow) == + "1 year old"); + CHECK(LLDateUtil::ageFromDate("12/31/2008", mNow) == + "1 year old"); } - template<> template<> - void dateutil_object_t::test<2>() + TEST_CASE_METHOD(dateutil, "LLDateUtil: an age of a month to a year reads in whole months", "[LLDateUtil]") { - set_test_name("Months"); - ensure_equals("months", - LLDateUtil::ageFromDate("10/30/2009", mNow), - "2 months old" ); - ensure_equals("months 2", - LLDateUtil::ageFromDate("10/31/2009", mNow), - "2 months old" ); - ensure_equals("single month", - LLDateUtil::ageFromDate("11/30/2009", mNow), - "1 month old" ); + CHECK(LLDateUtil::ageFromDate("10/30/2009", mNow) == + "2 months old"); + CHECK(LLDateUtil::ageFromDate("10/31/2009", mNow) == + "2 months old"); + CHECK(LLDateUtil::ageFromDate("11/30/2009", mNow) == + "1 month old"); } - template<> template<> - void dateutil_object_t::test<3>() + TEST_CASE_METHOD(dateutil, "LLDateUtil: an age under a month reads in whole weeks", "[LLDateUtil]") { - set_test_name("Weeks"); - ensure_equals("4 weeks", - LLDateUtil::ageFromDate("12/1/2009", mNow), - "4 weeks old" ); - ensure_equals("weeks", - LLDateUtil::ageFromDate("12/17/2009", mNow), - "2 weeks old" ); - ensure_equals("single week", - LLDateUtil::ageFromDate("12/24/2009", mNow), - "1 week old" ); + CHECK(LLDateUtil::ageFromDate("12/1/2009", mNow) == + "4 weeks old"); + CHECK(LLDateUtil::ageFromDate("12/17/2009", mNow) == + "2 weeks old"); + CHECK(LLDateUtil::ageFromDate("12/24/2009", mNow) == + "1 week old"); } - template<> template<> - void dateutil_object_t::test<4>() + TEST_CASE_METHOD(dateutil, "LLDateUtil: an age under a week reads in days or as joined today", "[LLDateUtil]") { - set_test_name("Days"); - ensure_equals("days", - LLDateUtil::ageFromDate("12/29/2009", mNow), - "2 days old" ); - ensure_equals("single day", - LLDateUtil::ageFromDate("12/30/2009", mNow), - "1 day old" ); - ensure_equals("today", - LLDateUtil::ageFromDate("12/31/2009", mNow), - "Joined today" ); + CHECK(LLDateUtil::ageFromDate("12/29/2009", mNow) == + "2 days old"); + CHECK(LLDateUtil::ageFromDate("12/30/2009", mNow) == + "1 day old"); + CHECK(LLDateUtil::ageFromDate("12/31/2009", mNow) == + "Joined today"); } - template<> template<> - void dateutil_object_t::test<5>() + TEST_CASE_METHOD(dateutil, "LLDateUtil: an age that spans the new year still reads in weeks", "[LLDateUtil]") { - set_test_name("2010 rollover"); LLDate now(std::string("2010-01-04T12:00:00Z")); - ensure_equals("days", - LLDateUtil::ageFromDate("12/13/2009", now), - "3 weeks old" ); + CHECK(LLDateUtil::ageFromDate("12/13/2009", now) == + "3 weeks old"); } //template<> template<> diff --git a/indra/newview/tests/llhttpretrypolicy_test.cpp b/indra/newview/tests/llhttpretrypolicy_test.cpp index a261ec8699..2ed768cb56 100644 --- a/indra/newview/tests/llhttpretrypolicy_test.cpp +++ b/indra/newview/tests/llhttpretrypolicy_test.cpp @@ -26,35 +26,27 @@ #include "../llviewerprecompiledheaders.h" #include "../llhttpretrypolicy.h" -#include "lltut.h" +#include +#include +#include "altestmatchers.h" -namespace tut +namespace { -struct llhttpretrypolicy_data -{ -}; - -typedef test_group RetryPolicyTestGroup; -typedef RetryPolicyTestGroup::object RetryPolicyTestObject; -RetryPolicyTestGroup retryPolicyTestGroup("retry_policy"); - -template<> template<> -void RetryPolicyTestObject::test<1>() +TEST_CASE("retry_policy: a policy of zero retries never retries", "[retry_policy]") { LLAdaptiveRetryPolicy never_retry(1.0,1.0,1.0,0); LLSD headers; F32 wait_seconds; // No retry until we've failed a try. - ensure("never retry 0", !never_retry.shouldRetry(wait_seconds)); + CHECK_FALSE(never_retry.shouldRetry(wait_seconds)); // 0 retries max. never_retry.onFailure(500,headers); - ensure("never retry 1", !never_retry.shouldRetry(wait_seconds)); + CHECK_FALSE(never_retry.shouldRetry(wait_seconds)); } -template<> template<> -void RetryPolicyTestObject::test<2>() +TEST_CASE("retry_policy: a 404 is retried only when 4xx retries are enabled", "[retry_policy]") { LLSD headers; F32 wait_seconds; @@ -62,17 +54,16 @@ void RetryPolicyTestObject::test<2>() // Normally only retry on server error (5xx) LLAdaptiveRetryPolicy noRetry404(1.0,2.0,3.0,10); noRetry404.onFailure(404,headers); - ensure("no retry on 404", !noRetry404.shouldRetry(wait_seconds)); + CHECK_FALSE(noRetry404.shouldRetry(wait_seconds)); // Can retry on 4xx errors if enabled by flag. bool do_retry_4xx = true; LLAdaptiveRetryPolicy doRetry404(1.0,2.0,3.0,10,do_retry_4xx); doRetry404.onFailure(404,headers); - ensure("do retry on 404", doRetry404.shouldRetry(wait_seconds)); + CHECK(doRetry404.shouldRetry(wait_seconds)); } -template<> template<> -void RetryPolicyTestObject::test<3>() +TEST_CASE("retry_policy: waits double to a cap and retries stop at the limit until a success", "[retry_policy]") { // Should retry after 1.0, 2.0, 3.0, 3.0 seconds. LLAdaptiveRetryPolicy basic_retry(1.0,3.0,2.0,4); @@ -82,64 +73,62 @@ void RetryPolicyTestObject::test<3>() U32 frac_bits = 6; // No retry until we've failed a try. - ensure("basic_retry 0", !basic_retry.shouldRetry(wait_seconds)); + CHECK_FALSE(basic_retry.shouldRetry(wait_seconds)); // Starting wait 1.0 basic_retry.onFailure(500,headers); should_retry = basic_retry.shouldRetry(wait_seconds); - ensure("basic_retry 1", should_retry); - ensure_approximately_equals("basic_retry 1", wait_seconds, 1.0F, frac_bits); + CHECK(should_retry); + CHECK_THAT(wait_seconds, ll_test::WithinBits(1.0F, frac_bits)); // Double wait to 2.0 basic_retry.onFailure(500,headers); should_retry = basic_retry.shouldRetry(wait_seconds); - ensure("basic_retry 2", should_retry); - ensure_approximately_equals("basic_retry 2", wait_seconds, 2.0F, frac_bits); + CHECK(should_retry); + CHECK_THAT(wait_seconds, ll_test::WithinBits(2.0F, frac_bits)); // Hit max wait of 3.0 (4.0 clamped to max 3) basic_retry.onFailure(500,headers); should_retry = basic_retry.shouldRetry(wait_seconds); - ensure("basic_retry 3", should_retry); - ensure_approximately_equals("basic_retry 3", wait_seconds, 3.0F, frac_bits); + CHECK(should_retry); + CHECK_THAT(wait_seconds, ll_test::WithinBits(3.0F, frac_bits)); // At max wait, should stay at 3.0 basic_retry.onFailure(500,headers); should_retry = basic_retry.shouldRetry(wait_seconds); - ensure("basic_retry 4", should_retry); - ensure_approximately_equals("basic_retry 4", wait_seconds, 3.0F, frac_bits); + CHECK(should_retry); + CHECK_THAT(wait_seconds, ll_test::WithinBits(3.0F, frac_bits)); // Max retries, should fail now. basic_retry.onFailure(500,headers); should_retry = basic_retry.shouldRetry(wait_seconds); - ensure("basic_retry 5", !should_retry); + CHECK_FALSE(should_retry); // Max retries, should fail now. basic_retry.onFailure(500,headers); should_retry = basic_retry.shouldRetry(wait_seconds); - ensure("basic_retry 5", !should_retry); + CHECK_FALSE(should_retry); // After a success, should reset to the starting state. basic_retry.onSuccess(); // No retry until we've failed a try. - ensure("basic_retry 6", !basic_retry.shouldRetry(wait_seconds)); + CHECK_FALSE(basic_retry.shouldRetry(wait_seconds)); // Starting wait 1.0 basic_retry.onFailure(500,headers); should_retry = basic_retry.shouldRetry(wait_seconds); - ensure("basic_retry 7", should_retry); - ensure_approximately_equals("basic_retry 7", wait_seconds, 1.0F, frac_bits); + CHECK(should_retry); + CHECK_THAT(wait_seconds, ll_test::WithinBits(1.0F, frac_bits)); // Double wait to 2.0 basic_retry.onFailure(500,headers); should_retry = basic_retry.shouldRetry(wait_seconds); - ensure("basic_retry 8", should_retry); - ensure_approximately_equals("basic_retry 8", wait_seconds, 2.0F, frac_bits); + CHECK(should_retry); + CHECK_THAT(wait_seconds, ll_test::WithinBits(2.0F, frac_bits)); } -// Retries should stop as soon as a non-5xx error is received. -template<> template<> -void RetryPolicyTestObject::test<4>() +TEST_CASE("retry_policy: retries stop for good at the first non-5xx error", "[retry_policy]") { // Should retry after 1.0, 2.0, 3.0, 3.0 seconds. LLAdaptiveRetryPolicy killer404(1.0,3.0,2.0,4); @@ -151,24 +140,24 @@ void RetryPolicyTestObject::test<4>() // Starting wait 1.0 killer404.onFailure(500,headers); should_retry = killer404.shouldRetry(wait_seconds); - ensure("killer404 1", should_retry); - ensure_approximately_equals("killer404 1", wait_seconds, 1.0F, frac_bits); + CHECK(should_retry); + CHECK_THAT(wait_seconds, ll_test::WithinBits(1.0F, frac_bits)); // Double wait to 2.0 killer404.onFailure(500,headers); should_retry = killer404.shouldRetry(wait_seconds); - ensure("killer404 2", should_retry); - ensure_approximately_equals("killer404 2", wait_seconds, 2.0F, frac_bits); + CHECK(should_retry); + CHECK_THAT(wait_seconds, ll_test::WithinBits(2.0F, frac_bits)); // Should fail on non-5xx killer404.onFailure(404,headers); should_retry = killer404.shouldRetry(wait_seconds); - ensure("killer404 3", !should_retry); + CHECK_FALSE(should_retry); // After a non-5xx, should keep failing. killer404.onFailure(500,headers); should_retry = killer404.shouldRetry(wait_seconds); - ensure("killer404 4", !should_retry); + CHECK_FALSE(should_retry); } // Test handling of "retry-after" header. If present, this header @@ -176,8 +165,7 @@ void RetryPolicyTestObject::test<4>() // progression of delay values. For example, if the normal // progression of delays would be 1,2,4,8..., but the 2nd and 3rd calls // get a retry header of 33, the pattern would become 1,33,33,8... -template<> template<> -void RetryPolicyTestObject::test<5>() +TEST_CASE("retry_policy: a retry-after header overrides the wait but not its progression", "[retry_policy]") { LLAdaptiveRetryPolicy policy(1.0,25.0,2.0,6); LLSD headers_with_retry; @@ -189,45 +177,44 @@ void RetryPolicyTestObject::test<5>() policy.onFailure(500,headers_without_retry); should_retry = policy.shouldRetry(wait_seconds); - ensure("retry header 1", should_retry); - ensure_approximately_equals("retry header 1", wait_seconds, 1.0F, frac_bits); + CHECK(should_retry); + CHECK_THAT(wait_seconds, ll_test::WithinBits(1.0F, frac_bits)); policy.onFailure(500,headers_without_retry); should_retry = policy.shouldRetry(wait_seconds); - ensure("retry header 2", should_retry); - ensure_approximately_equals("retry header 2", wait_seconds, 2.0F, frac_bits); + CHECK(should_retry); + CHECK_THAT(wait_seconds, ll_test::WithinBits(2.0F, frac_bits)); policy.onFailure(500,headers_with_retry); should_retry = policy.shouldRetry(wait_seconds); - ensure("retry header 3", should_retry); + CHECK(should_retry); // 4.0 overrides by header -> 666.0 - ensure_approximately_equals("retry header 3", wait_seconds, 666.0F, frac_bits); + CHECK_THAT(wait_seconds, ll_test::WithinBits(666.0F, frac_bits)); policy.onFailure(500,headers_with_retry); should_retry = policy.shouldRetry(wait_seconds); - ensure("retry header 4", should_retry); + CHECK(should_retry); // 8.0 overrides by header -> 666.0 - ensure_approximately_equals("retry header 4", wait_seconds, 666.0F, frac_bits); + CHECK_THAT(wait_seconds, ll_test::WithinBits(666.0F, frac_bits)); policy.onFailure(500,headers_without_retry); should_retry = policy.shouldRetry(wait_seconds); - ensure("retry header 5", should_retry); - ensure_approximately_equals("retry header 5", wait_seconds, 16.0F, frac_bits); + CHECK(should_retry); + CHECK_THAT(wait_seconds, ll_test::WithinBits(16.0F, frac_bits)); policy.onFailure(500,headers_without_retry); should_retry = policy.shouldRetry(wait_seconds); - ensure("retry header 6", should_retry); - ensure_approximately_equals("retry header 6", wait_seconds, 25.0F, frac_bits); + CHECK(should_retry); + CHECK_THAT(wait_seconds, ll_test::WithinBits(25.0F, frac_bits)); policy.onFailure(500,headers_with_retry); should_retry = policy.shouldRetry(wait_seconds); - ensure("retry header 7", !should_retry); + CHECK_FALSE(should_retry); } // Test getSecondsUntilRetryAfter(const std::string& retry_after, F32& seconds_to_wait), // used by header parsing of the retry policy. -template<> template<> -void RetryPolicyTestObject::test<6>() +TEST_CASE("retry_policy: getSecondsUntilRetryAfter parses seconds and an HTTP date", "[retry_policy]") { F32 seconds_to_wait; bool success; @@ -235,14 +222,14 @@ void RetryPolicyTestObject::test<6>() std::string str1("0"); seconds_to_wait = F32_MAX; success = LLAdaptiveRetryPolicy::getSecondsUntilRetryAfter(str1, seconds_to_wait); - ensure("parse 1", success); - ensure_equals("parse 1", seconds_to_wait, 0.0); + CHECK(success); + CHECK(seconds_to_wait == 0.0); std::string str2("999.9"); seconds_to_wait = F32_MAX; success = LLAdaptiveRetryPolicy::getSecondsUntilRetryAfter(str2, seconds_to_wait); - ensure("parse 2", success); - ensure_approximately_equals("parse 2", seconds_to_wait, 999.9F, 8); + CHECK(success); + CHECK_THAT(seconds_to_wait, ll_test::WithinBits(999.9F, 8)); time_t nowseconds; time(&nowseconds); @@ -250,13 +237,11 @@ void RetryPolicyTestObject::test<6>() seconds_to_wait = F32_MAX; success = LLAdaptiveRetryPolicy::getSecondsUntilRetryAfter(str3, seconds_to_wait); std::cerr << " str3 [" << str3 << "]" << std::endl; - ensure("parse 3", success); - ensure_approximately_equals_range("parse 3", seconds_to_wait, 44.0F, 2.0F); + CHECK(success); + CHECK_THAT(seconds_to_wait, Catch::Matchers::WithinAbs(44.0F, 2.0F)); } -// Test retry-after field in both llmessage and CoreHttp headers. -template<> template<> -void RetryPolicyTestObject::test<7>() +TEST_CASE("retry_policy: retry-after is honoured in both llmessage and CoreHttp headers", "[retry_policy]") { std::cerr << "7 starts" << std::endl; @@ -268,21 +253,21 @@ void RetryPolicyTestObject::test<7>() bool should_retry; // No retry until we've failed a try. - ensure("header 0", !policy.shouldRetry(seconds_to_wait)); + CHECK_FALSE(policy.shouldRetry(seconds_to_wait)); // no retry header, use default. policy.onFailure(500,LLSD()); should_retry = policy.shouldRetry(seconds_to_wait); - ensure("header 1", should_retry); - ensure_approximately_equals("header 1", seconds_to_wait, 17.0F, 6); + CHECK(should_retry); + CHECK_THAT(seconds_to_wait, ll_test::WithinBits(17.0F, 6)); // retry header should override, give delay of 0 std::string date_string = LLDate((F64)(nowseconds+7)).asRFC1123(); sd_headers[HTTP_IN_HEADER_RETRY_AFTER] = date_string; policy.onFailure(503,sd_headers); should_retry = policy.shouldRetry(seconds_to_wait); - ensure("header 2", should_retry); - ensure_approximately_equals_range("header 2", seconds_to_wait, 7.0F, 2.0F); + CHECK(should_retry); + CHECK_THAT(seconds_to_wait, Catch::Matchers::WithinAbs(7.0F, 2.0F)); LLCore::HttpResponse *response; LLCore::HttpHeaders::ptr_t headers; @@ -294,8 +279,8 @@ void RetryPolicyTestObject::test<7>() headers->append(HTTP_IN_HEADER_RETRY_AFTER, std::string("600")); policy.onFailure(response); should_retry = policy.shouldRetry(seconds_to_wait); - ensure("header 3",should_retry); - ensure_approximately_equals("header 3", seconds_to_wait, 600.0F, 6); + CHECK(should_retry); + CHECK_THAT(seconds_to_wait, ll_test::WithinBits(600.0F, 6)); response->release(); response = new LLCore::HttpResponse(); @@ -308,20 +293,20 @@ void RetryPolicyTestObject::test<7>() headers->append(HTTP_IN_HEADER_RETRY_AFTER,date_string); policy.onFailure(response); should_retry = policy.shouldRetry(seconds_to_wait); - ensure("header 4",should_retry); - ensure_approximately_equals_range("header 4", seconds_to_wait, 77.0F, 2.0F); + CHECK(should_retry); + CHECK_THAT(seconds_to_wait, Catch::Matchers::WithinAbs(77.0F, 2.0F)); response->release(); // Timeout should be clamped at max. policy.onFailure(500,LLSD()); should_retry = policy.shouldRetry(seconds_to_wait); - ensure("header 5", should_retry); - ensure_approximately_equals("header 5", seconds_to_wait, 644.0F, 6); + CHECK(should_retry); + CHECK_THAT(seconds_to_wait, ll_test::WithinBits(644.0F, 6)); // No more retries. policy.onFailure(500,LLSD()); should_retry = policy.shouldRetry(seconds_to_wait); - ensure("header 6", !should_retry); + CHECK_FALSE(should_retry); } } diff --git a/indra/newview/tests/lllogininstance_test.cpp b/indra/newview/tests/lllogininstance_test.cpp index b1a2db7065..4c02155661 100644 --- a/indra/newview/tests/lllogininstance_test.cpp +++ b/indra/newview/tests/lllogininstance_test.cpp @@ -38,7 +38,7 @@ // std headers // external library headers // other Linden headers -#include "../test/lltut.h" +#include #include "llevents.h" #include "llnotificationsutil.h" #include "lltrans.h" @@ -338,7 +338,7 @@ std::string xml_escape_string(const std::string& in) /***************************************************************************** * TUT *****************************************************************************/ -namespace tut +namespace { struct lllogininstance_data { @@ -394,19 +394,13 @@ namespace tut MockNotifications notifications; }; - typedef test_group lllogininstance_group; - typedef lllogininstance_group::object lllogininstance_object; - lllogininstance_group llsdmgr("LLLoginInstance"); - - template<> template<> - void lllogininstance_object::test<1>() + TEST_CASE_METHOD(lllogininstance_data, "LLLoginInstance: a successful connect keeps the response until a disconnect", "[LLLoginInstance]") { - set_test_name("Test Simple Success And Disconnect"); - // Test default connect. logininstance->connect(agentCredential); - ensure_equals("Default connect uri", gLoginURI, VIEWERLOGIN_URI); + UNSCOPED_INFO("Default connect uri"); + CHECK(gLoginURI == VIEWERLOGIN_URI); // Dummy success response. LLSD response; @@ -418,12 +412,12 @@ namespace tut testPump().post(response); - ensure("Success response", logininstance->authSuccess()); - ensure_equals("Test Response Data", logininstance->getResponse().asString(), "test_data"); + CHECK(logininstance->authSuccess()); + CHECK(logininstance->getResponse().asString() == "test_data"); logininstance->disconnect(); - ensure_equals("Called Login Module Disconnect", gDisconnectCalled, true); + CHECK(gDisconnectCalled); response.clear(); response["state"] = "offline"; @@ -434,23 +428,23 @@ namespace tut testPump().post(response); - ensure("Disconnected", !(logininstance->authSuccess())); + UNSCOPED_INFO("Disconnected"); + CHECK_FALSE(logininstance->authSuccess()); } - template<> template<> - void lllogininstance_object::test<2>() + TEST_CASE_METHOD(lllogininstance_data, "LLLoginInstance: accepting the TOS or a critical message reconnects with it agreed", "[LLLoginInstance]") { - set_test_name("Test User TOS/Critical message Interaction"); - const std::string test_uri = "testing-uri"; // Test default connect. logininstance->connect(test_uri, agentCredential); // connect should call LLLogin::connect to init gLoginURI and gLoginCreds. - ensure_equals("Default connect uri", gLoginURI, "testing-uri"); - ensure_equals("Default for agree to tos", gLoginCreds["params"]["agree_to_tos"].asBoolean(), false); - ensure_equals("Default for read critical", gLoginCreds["params"]["read_critical"].asBoolean(), false); + CHECK(gLoginURI == "testing-uri"); + UNSCOPED_INFO("Default for agree to tos"); + CHECK_FALSE(gLoginCreds["params"]["agree_to_tos"].asBoolean()); + UNSCOPED_INFO("Default for read critical"); + CHECK_FALSE(gLoginCreds["params"]["read_critical"].asBoolean()); // TOS failure response. LLSD response; @@ -461,43 +455,62 @@ namespace tut response["data"]["reason"] = "tos"; testPump().post(response); - ensure_equals("TOS Dialog type", gTOSType, "message_tos"); - ensure("TOS callback given", gTOSReplyPump != 0); + UNSCOPED_INFO("TOS Dialog type"); + CHECK(gTOSType == "message_tos"); + UNSCOPED_INFO("TOS callback given"); + REQUIRE(gTOSReplyPump != 0); gTOSReplyPump->post(false); // Call callback denying TOS. - ensure("No TOS, failed auth", logininstance->authFailure()); + UNSCOPED_INFO("No TOS, failed auth"); + CHECK(logininstance->authFailure()); // Start again. logininstance->connect(test_uri, agentCredential); testPump().post(response); // Fail for tos again. gTOSReplyPump->post(true); // Accept tos, should reconnect w/ agree_to_tos. - ensure_equals("Accepted agree to tos", gLoginCreds["params"]["agree_to_tos"].asBoolean(), true); - ensure("Incomplete login status", !logininstance->authFailure() && !logininstance->authSuccess()); + UNSCOPED_INFO("Accepted agree to tos"); + CHECK(gLoginCreds["params"]["agree_to_tos"].asBoolean()); + { + INFO("Incomplete login status"); + REQUIRE_FALSE(logininstance->authFailure()); + CHECK_FALSE(logininstance->authSuccess()); + } // Fail connection, attempt connect again. // The new request should have reset agree to tos to default. response["data"]["reason"] = "key"; // bad creds. testPump().post(response); - ensure("TOS auth failure", logininstance->authFailure()); + UNSCOPED_INFO("Bad credentials, failed auth"); + CHECK(logininstance->authFailure()); logininstance->connect(test_uri, agentCredential); - ensure_equals("Reset to default for agree to tos", gLoginCreds["params"]["agree_to_tos"].asBoolean(), false); + UNSCOPED_INFO("Reset to default for agree to tos"); + CHECK_FALSE(gLoginCreds["params"]["agree_to_tos"].asBoolean()); // Critical Message failure response. logininstance->connect(test_uri, agentCredential); response["data"]["reason"] = "critical"; // Change response to "critical message" testPump().post(response); - ensure_equals("TOS Dialog type", gTOSType, "message_critical"); - ensure("TOS callback given", gTOSReplyPump != 0); + UNSCOPED_INFO("TOS Dialog type"); + CHECK(gTOSType == "message_critical"); + UNSCOPED_INFO("TOS callback given"); + REQUIRE(gTOSReplyPump != 0); gTOSReplyPump->post(true); - ensure_equals("Accepted read critical message", gLoginCreds["params"]["read_critical"].asBoolean(), true); - ensure("Incomplete login status", !logininstance->authFailure() && !logininstance->authSuccess()); + UNSCOPED_INFO("Accepted read critical message"); + CHECK(gLoginCreds["params"]["read_critical"].asBoolean()); + { + INFO("Incomplete login status"); + REQUIRE_FALSE(logininstance->authFailure()); + CHECK_FALSE(logininstance->authSuccess()); + } // Fail then attempt new connection response["data"]["reason"] = "key"; // bad creds. testPump().post(response); - ensure("TOS auth failure", logininstance->authFailure()); + UNSCOPED_INFO("Bad credentials, failed auth"); + CHECK(logininstance->authFailure()); logininstance->connect(test_uri, agentCredential); - ensure_equals("Default for agree to tos", gLoginCreds["params"]["read_critical"].asBoolean(), false); + UNSCOPED_INFO("Reset to default for read critical"); + CHECK_FALSE(gLoginCreds["params"]["read_critical"].asBoolean()); } } diff --git a/indra/newview/tests/llremoteparcelrequest_test.cpp b/indra/newview/tests/llremoteparcelrequest_test.cpp index d1eae88f46..76bdad4bca 100644 --- a/indra/newview/tests/llremoteparcelrequest_test.cpp +++ b/indra/newview/tests/llremoteparcelrequest_test.cpp @@ -26,7 +26,7 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../llremoteparcelrequest.h" @@ -52,8 +52,6 @@ void LLAgent::sendReliableMessage(void) { } LLUUID gAgentSessionID; LLUUID gAgentID; -namespace tut -{ namespace { struct TestObserver : public LLRemoteParcelInfoObserver { @@ -70,7 +68,6 @@ namespace bool mProcessed; }; -} // anonymous namespace struct RemoteParcelRequestData { @@ -79,15 +76,8 @@ namespace } }; - typedef test_group remoteparcelrequest_t; - typedef remoteparcelrequest_t::object remoteparcelrequest_object_t; - tut::remoteparcelrequest_t tut_remoteparcelrequest("LLRemoteParcelRequest"); - - template<> template<> - void remoteparcelrequest_object_t::test<1>() + TEST_CASE_METHOD(RemoteParcelRequestData, "LLRemoteParcelRequest: an observer is told of the reply for its parcel", "[LLRemoteParcelRequest]") { - set_test_name("observer pointer"); - std::unique_ptr observer(new TestObserver()); LLRemoteParcelInfoProcessor & processor = LLRemoteParcelInfoProcessor::instance(); @@ -95,14 +85,12 @@ namespace processor.processParcelData(testParcelData()); - ensure(observer->mProcessed); + CHECK(observer->mProcessed); } - template<> template<> - void remoteparcelrequest_object_t::test<2>() + // CHOP-220: a dangling observer pointer. + TEST_CASE_METHOD(RemoteParcelRequestData, "LLRemoteParcelRequest: a reply after its observer is deleted does not crash", "[LLRemoteParcelRequest]") { - set_test_name("CHOP-220: dangling observer pointer"); - LLRemoteParcelInfoObserver * observer = new TestObserver(); LLRemoteParcelInfoProcessor & processor = LLRemoteParcelInfoProcessor::instance(); diff --git a/indra/newview/tests/llsecapi_test.cpp b/indra/newview/tests/llsecapi_test.cpp index f065d29458..2697db68ee 100644 --- a/indra/newview/tests/llsecapi_test.cpp +++ b/indra/newview/tests/llsecapi_test.cpp @@ -27,7 +27,7 @@ */ #include "../llviewerprecompiledheaders.h" #include "../llviewernetwork.h" -#include "../test/lltut.h" +#include #include "../llsecapi.h" #include "../llsechandler_basic.h" #include "../../llxml/llcontrol.h" @@ -81,7 +81,7 @@ void LLSecAPIBasicHandler::removeCredentialMap(const std::string& storage, const // ------------------------------------------------------------------------------------------- // TUT // ------------------------------------------------------------------------------------------- -namespace tut +namespace { // Test wrapper declaration : wrapping nothing for the moment struct secapiTest @@ -95,39 +95,29 @@ namespace tut } }; - // Tut templating thingamagic: test group, object and test instance - typedef test_group secapiTestFactory; - typedef secapiTestFactory::object secapiTestObject; - tut::secapiTestFactory tut_secapi("LLSecAPI"); - // --------------------------------------------------------------------------------------- // Test functions // --------------------------------------------------------------------------------------- - // registration - template<> template<> - void secapiTestObject::test<1>() + TEST_CASE_METHOD(secapiTest, "LLSecAPI: getSecHandler finds registered handlers by name and no others", "[LLSecAPI]") { // retrieve an unknown handler - ensure("'Unknown' handler should be NULL", getSecHandler("unknown") == nullptr); + REQUIRE(getSecHandler("unknown") == nullptr); LLPointer test1_handler = new LLSecAPIBasicHandler(); registerSecHandler("sectest1", test1_handler); - ensure("'Unknown' handler should be NULL", getSecHandler("unknown") == nullptr); + REQUIRE(getSecHandler("unknown") == nullptr); LLPointer retrieved_test1_handler = getSecHandler("sectest1"); - ensure("Retrieved sectest1 handler should be the same", - retrieved_test1_handler == test1_handler); + CHECK(retrieved_test1_handler == test1_handler); // insert a second handler LLPointer test2_handler = new LLSecAPIBasicHandler(); registerSecHandler("sectest2", test2_handler); - ensure("'Unknown' handler should be NULL", getSecHandler("unknown") == nullptr); + REQUIRE(getSecHandler("unknown") == nullptr); retrieved_test1_handler = getSecHandler("sectest1"); - ensure("Retrieved sectest1 handler should be the same", - retrieved_test1_handler == test1_handler); + CHECK(retrieved_test1_handler == test1_handler); LLPointer retrieved_test2_handler = getSecHandler("sectest2"); - ensure("Retrieved sectest1 handler should be the same", - retrieved_test2_handler == test2_handler); + CHECK(retrieved_test2_handler == test2_handler); } } diff --git a/indra/newview/tests/llsechandler_basic_test.cpp b/indra/newview/tests/llsechandler_basic_test.cpp index b9a51c67b3..34bba3d616 100644 --- a/indra/newview/tests/llsechandler_basic_test.cpp +++ b/indra/newview/tests/llsechandler_basic_test.cpp @@ -26,7 +26,8 @@ * $/LicenseInfo$ */ #include "../llviewerprecompiledheaders.h" -#include "../test/lltut.h" +#include +#include "altestmatchers.h" #include "../llsecapi.h" #include "../llsechandler_basic.h" #include "../../llxml/llcontrol.h" @@ -52,17 +53,18 @@ #include "../llmachineid.h" #include -#define ensure_throws(str, exc_type, cert, func, ...) \ +#define require_throws_with_cert(str, exc_type, cert, func, ...) \ try \ { \ func(__VA_ARGS__); \ -fail("throws, " str); \ +FAIL("no exception: " str); \ } \ catch(exc_type& except) \ { \ LLSD cert_data; \ cert->getLLSD(cert_data); \ -ensure("Exception cert is incorrect for " str, valueCompareLLSD(except.getCertData(), cert_data)); \ +UNSCOPED_INFO("Exception cert is incorrect for " str); \ +REQUIRE(valueCompareLLSD(except.getCertData(), cert_data)); \ } extern bool _cert_hostname_wildcard_match(const std::string& hostname, const std::string& wildcard_string); @@ -140,7 +142,7 @@ LLCertException::LLCertException(const LLSD& cert_data, const std::string& msg) // ------------------------------------------------------------------------------------------- // TUT // ------------------------------------------------------------------------------------------- -namespace tut +namespace { const std::string mPemTestCert( "Certificate:\n" @@ -702,17 +704,10 @@ namespace tut } }; - // Tut templating thingamagic: test group, object and test instance - typedef test_group sechandler_basic_test_factory; - typedef sechandler_basic_test_factory::object sechandler_basic_test_object; - tut::sechandler_basic_test_factory tut_sechandler_basic("LLSecHandler"); - // --------------------------------------------------------------------------------------- // Test functions // --------------------------------------------------------------------------------------- - // test cert data retrieval - template<> template<> - void sechandler_basic_test_object::test<1>() + TEST_CASE_METHOD(sechandler_basic_test, "LLSecHandler: a certificate is made from PEM without throwing", "[LLSecHandler]") { try { @@ -722,17 +717,16 @@ namespace tut catch (LLCertException& cert_exception) { LL_INFOS() << "cert ex: " << cert_exception.getCertData() << LL_ENDL; - fail("cert exception"); + FAIL("cert exception"); } catch (...) { LOG_UNHANDLED_EXCEPTION("test 1"); - fail("other exception"); + FAIL("other exception"); } } - template<> template<> - void sechandler_basic_test_object::test<2>() + TEST_CASE_METHOD(sechandler_basic_test, "LLSecHandler: a certificate's LLSD describes its issuer and subject and validity", "[LLSecHandler]") { LLPointer test_cert(new LLBasicCertificate(mPemChildCert, &mValidationDate)); @@ -741,41 +735,38 @@ namespace tut //std::ostringstream llsd_value; //llsd_value << LLSDOStreamer(llsd_cert) << std::endl; LL_DEBUGS() << "test 1 cert " << llsd_cert << LL_ENDL; - ensure_equals("Issuer Name/commonName", (std::string)llsd_cert["issuer_name"]["commonName"], "Integration Test Intermediate CA"); - ensure_equals("Issuer Name/countryName", (std::string)llsd_cert["issuer_name"]["countryName"], "US"); - ensure_equals("Issuer Name/state", (std::string)llsd_cert["issuer_name"]["stateOrProvinceName"], "California"); - ensure_equals("Issuer Name/org name", (std::string)llsd_cert["issuer_name"]["organizationName"], "Linden Lab"); - ensure_equals("Issuer Name/org unit", (std::string)llsd_cert["issuer_name"]["organizationalUnitName"], "Second Life Engineering"); - ensure_equals("Issuer name string", (std::string)llsd_cert["issuer_name_string"], + CHECK((std::string)llsd_cert["issuer_name"]["commonName"] == "Integration Test Intermediate CA"); + CHECK((std::string)llsd_cert["issuer_name"]["countryName"] == "US"); + CHECK((std::string)llsd_cert["issuer_name"]["stateOrProvinceName"] == "California"); + CHECK((std::string)llsd_cert["issuer_name"]["organizationName"] == "Linden Lab"); + CHECK((std::string)llsd_cert["issuer_name"]["organizationalUnitName"] == "Second Life Engineering"); + CHECK((std::string)llsd_cert["issuer_name_string"] == "emailAddress=noreply@lindenlab.com,CN=Integration Test Intermediate CA,OU=Second Life Engineering,O=Linden Lab,L=San Francisco,ST=California,C=US"); - ensure_equals("subject Name/commonName", (std::string)llsd_cert["subject_name"]["commonName"], + CHECK((std::string)llsd_cert["subject_name"]["commonName"] == "Integration Test Server Cert"); - ensure_equals("subject Name/countryName", (std::string)llsd_cert["subject_name"]["countryName"], "US"); - ensure_equals("subject Name/state", (std::string)llsd_cert["subject_name"]["stateOrProvinceName"], "California"); - ensure_equals("subject Name/localityName", (std::string)llsd_cert["subject_name"]["localityName"], "San Francisco"); - ensure_equals("subject Name/org name", (std::string)llsd_cert["subject_name"]["organizationName"], "Linden Lab"); - ensure_equals("subjectName/org unit", - (std::string)llsd_cert["subject_name"]["organizationalUnitName"], "Second Life Engineering"); - - ensure_equals("subject name string", - (std::string)llsd_cert["subject_name_string"], + CHECK((std::string)llsd_cert["subject_name"]["countryName"] == "US"); + CHECK((std::string)llsd_cert["subject_name"]["stateOrProvinceName"] == "California"); + CHECK((std::string)llsd_cert["subject_name"]["localityName"] == "San Francisco"); + CHECK((std::string)llsd_cert["subject_name"]["organizationName"] == "Linden Lab"); + CHECK((std::string)llsd_cert["subject_name"]["organizationalUnitName"] == "Second Life Engineering"); + + CHECK((std::string)llsd_cert["subject_name_string"] == "emailAddress=noreply@lindenlab.com,CN=Integration Test Server Cert,OU=Second Life Engineering,O=Linden Lab,L=San Francisco,ST=California,C=US"); - ensure_equals("serial number", (std::string)llsd_cert["serial_number"], "9E8D3413E79BF931"); - ensure_equals("valid from", (std::string)llsd_cert["valid_from"], "2024-07-23T11:46:39Z"); - ensure_equals("valid to", (std::string)llsd_cert["valid_to"], "2034-07-21T11:46:39Z"); + CHECK((std::string)llsd_cert["serial_number"] == "9E8D3413E79BF931"); + CHECK((std::string)llsd_cert["valid_from"] == "2024-07-23T11:46:39Z"); + CHECK((std::string)llsd_cert["valid_to"] == "2034-07-21T11:46:39Z"); LLSD expectedKeyUsage = LLSD::emptyArray(); expectedKeyUsage.append(LLSD((std::string)"digitalSignature")); expectedKeyUsage.append(LLSD((std::string)"keyEncipherment")); - ensure("key usage", valueCompareLLSD(llsd_cert["keyUsage"], expectedKeyUsage)); - ensure_equals("basic constraints", llsd_cert["basicConstraints"]["CA"].asInteger(), 0); + CHECK(valueCompareLLSD(llsd_cert["keyUsage"], expectedKeyUsage)); + CHECK(llsd_cert["basicConstraints"]["CA"].asInteger() == 0); - ensure("x509 is equal", !X509_cmp(mX509ChildCert, test_cert->getOpenSSLX509())); + UNSCOPED_INFO("x509 is equal"); + CHECK_FALSE(X509_cmp(mX509ChildCert, test_cert->getOpenSSLX509())); } - // test protected data - template<> template<> - void sechandler_basic_test_object::test<3>() + TEST_CASE_METHOD(sechandler_basic_test, "LLSecHandler: protected data is kept by type and id and survives a reload", "[LLSecHandler]") { std::string protected_data = "sUSh3wj77NG9oAMyt3XIhaej3KLZhLZWFZvI6rIGmwUUOmmelrRg0NI9rkOj8ZDpTPxpwToaBT5u" "GQhakdaGLJznr9bHr4/6HIC1bouKj4n2rs4TL6j2WSjto114QdlNfLsE8cbbE+ghww58g8SeyLQO" @@ -789,7 +780,7 @@ namespace tut simdutf::result r = simdutf::base64_to_binary(protected_data.data(), protected_data.size(), (char*)binary_data.data()); if (r.error != simdutf::error_code::SUCCESS) { - fail("base64 decode failed"); + FAIL("base64 decode failed"); } else { @@ -813,9 +804,9 @@ namespace tut LLSD data = handler->getProtectedData("test_data_type", "test_data_id"); - ensure_equals("retrieve existing data1", (std::string)data["data1"], "test_data_1"); - ensure_equals("retrieve existing data2", (std::string)data["data2"], "test_data_2"); - ensure_equals("retrieve existing data3", (std::string)data["data3"]["elem1"], "test element1"); + CHECK((std::string)data["data1"] == "test_data_1"); + CHECK((std::string)data["data2"] == "test_data_2"); + CHECK((std::string)data["data3"]["elem1"] == "test element1"); // data storage LLSD store_data = LLSD::emptyMap(); @@ -829,22 +820,22 @@ namespace tut data = handler->getProtectedData("test_data_type", "test_data_id"); // verify no overwrite of existing data - ensure_equals("verify no overwrite 1", (std::string)data["data1"], "test_data_1"); - ensure_equals("verify no overwrite 2", (std::string)data["data2"], "test_data_2"); - ensure_equals("verify no overwrite 3", (std::string)data["data3"]["elem1"], "test element1"); + CHECK((std::string)data["data1"] == "test_data_1"); + CHECK((std::string)data["data2"] == "test_data_2"); + CHECK((std::string)data["data3"]["elem1"] == "test element1"); // verify written data is good data = handler->getProtectedData("test_data_type", "test_data_id1"); - ensure_equals("verify stored data1", (std::string)data["store_data1"], "test_store_data1"); - ensure_equals("verify stored data2", (int)data["store_data2"], 27); - ensure_equals("verify stored data3", (std::string)data["store_data3"]["subelem1"], "test_subelem1"); + CHECK((std::string)data["store_data1"] == "test_store_data1"); + CHECK((int)data["store_data2"] == 27); + CHECK((std::string)data["store_data3"]["subelem1"] == "test_subelem1"); // verify overwrite works handler->setProtectedData("test_data_type", "test_data_id", store_data); data = handler->getProtectedData("test_data_type", "test_data_id"); - ensure_equals("verify overwrite stored data1", (std::string)data["store_data1"], "test_store_data1"); - ensure_equals("verify overwrite stored data2", (int)data["store_data2"], 27); - ensure_equals("verify overwrite stored data3", (std::string)data["store_data3"]["subelem1"], "test_subelem1"); + CHECK((std::string)data["store_data1"] == "test_store_data1"); + CHECK((int)data["store_data2"] == 27); + CHECK((std::string)data["store_data3"]["subelem1"] == "test_subelem1"); // verify other datatype doesn't conflict store_data["store_data3"] = "test_store_data3"; @@ -854,14 +845,14 @@ namespace tut handler->setProtectedData("test_data_type1", "test_data_id", store_data); data = handler->getProtectedData("test_data_type1", "test_data_id"); - ensure_equals("verify datatype stored data3", (std::string)data["store_data3"], "test_store_data3"); - ensure_equals("verify datatype stored data4", (int)data["store_data4"], 28); - ensure_equals("verify datatype stored data5", (std::string)data["store_data5"]["subelem2"], "test_subelem2"); + CHECK((std::string)data["store_data3"] == "test_store_data3"); + CHECK((int)data["store_data4"] == 28); + CHECK((std::string)data["store_data5"]["subelem2"] == "test_subelem2"); // test data not found data = handler->getProtectedData("test_data_type1", "test_data_not_found"); - ensure("not found", data.isUndefined()); + REQUIRE(data.isUndefined()); // cause a 'write' by using 'LLPointer' to delete then instantiate a handler handler = NULL; @@ -869,9 +860,9 @@ namespace tut handler->init(); data = handler->getProtectedData("test_data_type1", "test_data_id"); - ensure_equals("verify datatype stored data3a", (std::string)data["store_data3"], "test_store_data3"); - ensure_equals("verify datatype stored data4a", (int)data["store_data4"], 28); - ensure_equals("verify datatype stored data5a", (std::string)data["store_data5"]["subelem2"], "test_subelem2"); + CHECK((std::string)data["store_data3"] == "test_store_data3"); + CHECK((int)data["store_data4"] == 28); + CHECK((std::string)data["store_data5"]["subelem2"] == "test_subelem2"); // rewrite the initial file to verify reloads handler = NULL; @@ -883,24 +874,22 @@ namespace tut handler = new LLSecAPIBasicHandler("sechandler_settings.tmp", "test_password.dat"); handler->init(); data = handler->getProtectedData("test_data_type1", "test_data_id"); - ensure("not found", data.isUndefined()); + REQUIRE(data.isUndefined()); handler->deleteProtectedData("test_data_type", "test_data_id"); - ensure("Deleted data not found", handler->getProtectedData("test_data_type", "test_data_id").isUndefined()); + REQUIRE(handler->getProtectedData("test_data_type", "test_data_id").isUndefined()); LLFile::remove("sechandler_settings.tmp"); handler = new LLSecAPIBasicHandler("sechandler_settings.tmp", "test_password.dat"); handler->init(); data = handler->getProtectedData("test_data_type1", "test_data_id"); - ensure("not found", data.isUndefined()); + REQUIRE(data.isUndefined()); handler = NULL; - ensure(LLFile::isfile("sechandler_settings.tmp")); + CHECK(LLFile::isfile("sechandler_settings.tmp")); } - // test credenitals - template<> template<> - void sechandler_basic_test_object::test<4>() + TEST_CASE_METHOD(sechandler_basic_test, "LLSecHandler: credentials save and load and delete and fall back to legacy logins", "[LLSecHandler]") { LLPointer handler = new LLSecAPIBasicHandler("sechandler_settings.tmp", "test_password.dat"); handler->init(); @@ -916,9 +905,12 @@ namespace tut LLPointer my_cred = handler->createCredential("my_grid", my_id, my_authenticator); // test retrieval of credential components - ensure_equals("basic credential creation: identifier", my_id, my_cred->getIdentifier()); - ensure_equals("basic credential creation: authenticator", my_authenticator, my_cred->getAuthenticator()); - ensure_equals("basic credential creation: grid", "my_grid", my_cred->getGrid()); + UNSCOPED_INFO("basic credential creation: identifier"); + CHECK_THAT(my_id, ll_test::LLSDEquals(my_cred->getIdentifier())); + UNSCOPED_INFO("basic credential creation: authenticator"); + CHECK_THAT(my_authenticator, ll_test::LLSDEquals(my_cred->getAuthenticator())); + UNSCOPED_INFO("basic credential creation: grid"); + CHECK(my_cred->getGrid() == "my_grid"); // test setting/overwriting of credential components my_id["first_name"] = "firstname"; @@ -927,53 +919,69 @@ namespace tut my_authenticator["hash"] = "6563245"; my_cred->setCredentialData(my_id, my_authenticator); - ensure_equals("set credential data: identifier", my_id, my_cred->getIdentifier()); - ensure_equals("set credential data: authenticator", my_authenticator, my_cred->getAuthenticator()); - ensure_equals("set credential data: grid", "my_grid", my_cred->getGrid()); + UNSCOPED_INFO("set credential data: identifier"); + CHECK_THAT(my_id, ll_test::LLSDEquals(my_cred->getIdentifier())); + UNSCOPED_INFO("set credential data: authenticator"); + CHECK_THAT(my_authenticator, ll_test::LLSDEquals(my_cred->getAuthenticator())); + UNSCOPED_INFO("set credential data: grid"); + CHECK(my_cred->getGrid() == "my_grid"); // test loading of a credential, that hasn't been saved, without // any legacy saved credential data LLPointer my_new_cred = handler->loadCredential("my_grid2"); - ensure("unknown credential load test", my_new_cred->getIdentifier().isMap()); - ensure("unknown credential load test", !my_new_cred->getIdentifier().has("type")); - ensure("unknown credential load test", my_new_cred->getAuthenticator().isMap()); - ensure("unknown credential load test", !my_new_cred->getAuthenticator().has("type")); + { + INFO("a credential never saved loads empty"); + REQUIRE(my_new_cred->getIdentifier().isMap()); + REQUIRE_FALSE(my_new_cred->getIdentifier().has("type")); + REQUIRE(my_new_cred->getAuthenticator().isMap()); + REQUIRE_FALSE(my_new_cred->getAuthenticator().has("type")); + } // test saving of a credential handler->saveCredential(my_cred, true); // test loading of a known credential my_new_cred = handler->loadCredential("my_grid"); - ensure_equals("load a known credential: identifier", my_id, my_new_cred->getIdentifier()); - ensure_equals("load a known credential: authenticator",my_authenticator, my_new_cred->getAuthenticator()); - ensure_equals("load a known credential: grid", "my_grid", my_cred->getGrid()); + UNSCOPED_INFO("load a known credential: identifier"); + CHECK_THAT(my_id, ll_test::LLSDEquals(my_new_cred->getIdentifier())); + UNSCOPED_INFO("load a known credential: authenticator"); + CHECK_THAT(my_authenticator, ll_test::LLSDEquals(my_new_cred->getAuthenticator())); + UNSCOPED_INFO("load a known credential: grid"); + CHECK(my_cred->getGrid() == "my_grid"); // test deletion of a credential handler->deleteCredential(my_new_cred); - ensure("delete credential: identifier", my_new_cred->getIdentifier().isUndefined()); - ensure("delete credentialt: authenticator", my_new_cred->getIdentifier().isUndefined()); - ensure_equals("delete credential: grid", "my_grid", my_cred->getGrid()); + UNSCOPED_INFO("delete credential: identifier"); + REQUIRE(my_new_cred->getIdentifier().isUndefined()); + UNSCOPED_INFO("delete credential: authenticator"); + REQUIRE(my_new_cred->getIdentifier().isUndefined()); + UNSCOPED_INFO("delete credential: grid"); + CHECK(my_cred->getGrid() == "my_grid"); // load unknown cred my_new_cred = handler->loadCredential("my_grid"); - ensure("deleted credential load test", my_new_cred->getIdentifier().isMap()); - ensure("deleted credential load test", !my_new_cred->getIdentifier().has("type")); - ensure("deleted credential load test", my_new_cred->getAuthenticator().isMap()); - ensure("deleted credential load test", !my_new_cred->getAuthenticator().has("type")); + { + INFO("a deleted credential loads empty"); + REQUIRE(my_new_cred->getIdentifier().isMap()); + REQUIRE_FALSE(my_new_cred->getIdentifier().has("type")); + REQUIRE(my_new_cred->getAuthenticator().isMap()); + REQUIRE_FALSE(my_new_cred->getAuthenticator().has("type")); + } // test loading of an unknown credential with legacy saved username, but without // saved password gFirstName = "myfirstname"; gLastName = "mylastname"; my_new_cred = handler->loadCredential("my_legacy_grid"); - ensure_equals("legacy credential with no password: type", - (const std::string)my_new_cred->getIdentifier()["type"], "agent"); - ensure_equals("legacy credential with no password: first_name", - (const std::string)my_new_cred->getIdentifier()["first_name"], "myfirstname"); - ensure_equals("legacy credential with no password: last_name", - (const std::string)my_new_cred->getIdentifier()["last_name"], "mylastname"); + UNSCOPED_INFO("legacy credential with no password: type"); + CHECK((const std::string)my_new_cred->getIdentifier()["type"] == "agent"); + UNSCOPED_INFO("legacy credential with no password: first_name"); + CHECK((const std::string)my_new_cred->getIdentifier()["first_name"] == "myfirstname"); + UNSCOPED_INFO("legacy credential with no password: last_name"); + CHECK((const std::string)my_new_cred->getIdentifier()["last_name"] == "mylastname"); - ensure("legacy credential with no password: no authenticator", my_new_cred->getAuthenticator().isUndefined()); + UNSCOPED_INFO("legacy credential with no password: no authenticator"); + REQUIRE(my_new_cred->getAuthenticator().isUndefined()); // test loading of an unknown credential with legacy saved password and username @@ -986,7 +994,7 @@ namespace tut simdutf::result r = simdutf::base64_to_binary(hashed_password.data(), hashed_password.size(), decoded_password.data()); if (r.error != simdutf::error_code::SUCCESS) { - fail("base64 decode failed"); + FAIL("base64 decode failed"); } else { @@ -1005,22 +1013,22 @@ namespace tut password_file.close(); my_new_cred = handler->loadCredential("my_legacy_grid2"); - ensure_equals("legacy credential with password: type", - (const std::string)my_new_cred->getIdentifier()["type"], "agent"); - ensure_equals("legacy credential with password: first_name", - (const std::string)my_new_cred->getIdentifier()["first_name"], "myfirstname"); - ensure_equals("legacy credential with password: last_name", - (const std::string)my_new_cred->getIdentifier()["last_name"], "mylastname"); + UNSCOPED_INFO("legacy credential with password: type"); + CHECK((const std::string)my_new_cred->getIdentifier()["type"] == "agent"); + UNSCOPED_INFO("legacy credential with password: first_name"); + CHECK((const std::string)my_new_cred->getIdentifier()["first_name"] == "myfirstname"); + UNSCOPED_INFO("legacy credential with password: last_name"); + CHECK((const std::string)my_new_cred->getIdentifier()["last_name"] == "mylastname"); LLSD legacy_authenticator = my_new_cred->getAuthenticator(); - ensure_equals("legacy credential with password: type", - (std::string)legacy_authenticator["type"], + UNSCOPED_INFO("legacy credential with password: authenticator type"); + CHECK((std::string)legacy_authenticator["type"] == "hash"); - ensure_equals("legacy credential with password: algorithm", - (std::string)legacy_authenticator["algorithm"], + UNSCOPED_INFO("legacy credential with password: algorithm"); + CHECK((std::string)legacy_authenticator["algorithm"] == "md5"); - ensure_equals("legacy credential with password: algorithm", - (std::string)legacy_authenticator["secret"], + UNSCOPED_INFO("legacy credential with password: secret"); + CHECK((std::string)legacy_authenticator["secret"] == "01234567890123456789012345678901"); // test creation of credentials @@ -1028,35 +1036,37 @@ namespace tut // test save without saving authenticator. handler->saveCredential(my_cred, false); my_new_cred = handler->loadCredential("mysavedgrid"); - ensure_equals("saved credential without auth", - (const std::string)my_new_cred->getIdentifier()["type"], "test_type"); - ensure("no authenticator values were saved", my_new_cred->getAuthenticator().isUndefined()); + UNSCOPED_INFO("saved credential without auth"); + CHECK((const std::string)my_new_cred->getIdentifier()["type"] == "test_type"); + UNSCOPED_INFO("no authenticator values were saved"); + REQUIRE(my_new_cred->getAuthenticator().isUndefined()); } - // test cert vector - template<> template<> - void sechandler_basic_test_object::test<5>() + TEST_CASE_METHOD(sechandler_basic_test, "LLSecHandler: a certificate vector adds once and iterates and inserts and finds", "[LLSecHandler]") { // validate create from empty vector LLPointer test_vector = new LLBasicCertificateVector(); - ensure_equals("when loading with nothing, we should result in no certs in vector", test_vector->size(), 0); + REQUIRE(test_vector->size() == 0); test_vector->add(new LLBasicCertificate(mPemTestCert, &mValidationDate)); - ensure_equals("one element in vector", test_vector->size(), 1); + REQUIRE(test_vector->size() == 1); test_vector->add(new LLBasicCertificate(mPemChildCert, &mValidationDate)); - ensure_equals("two elements in vector after add", test_vector->size(), 2); + REQUIRE(test_vector->size() == 2); // add duplicate; should be a no-op (and log at DEBUG level) test_vector->add(new LLBasicCertificate(mPemChildCert, &mValidationDate)); - ensure_equals("two elements in vector after re-add", test_vector->size(), 2); + UNSCOPED_INFO("two elements in vector after re-add"); + REQUIRE(test_vector->size() == 2); // validate order X509* test_cert = (*test_vector)[0]->getOpenSSLX509(); - ensure("first cert added remains first cert", !X509_cmp(test_cert, mX509TestCert)); + UNSCOPED_INFO("first cert added remains first cert"); + CHECK_FALSE(X509_cmp(test_cert, mX509TestCert)); X509_free(test_cert); test_cert = (*test_vector)[1]->getOpenSSLX509(); - ensure("second cert is second cert", !X509_cmp(test_cert, mX509ChildCert)); + UNSCOPED_INFO("second cert is second cert"); + CHECK_FALSE(X509_cmp(test_cert, mX509ChildCert)); X509_free(test_cert); // @@ -1065,71 +1075,75 @@ namespace tut LLBasicCertificateVector::iterator current_cert = test_vector->begin(); LLBasicCertificateVector::iterator copy_current_cert = current_cert; // operator++(int) - ensure("validate iterator++ element in vector is expected cert", *current_cert++ == (*test_vector)[0]); - ensure("validate 2nd iterator++ element in vector is expected cert", *current_cert++ == (*test_vector)[1]); - ensure("validate end iterator++", current_cert == test_vector->end()); + CHECK(*current_cert++ == (*test_vector)[0]); + CHECK(*current_cert++ == (*test_vector)[1]); + REQUIRE(current_cert == test_vector->end()); // copy - ensure("validate copy iterator element in vector is expected cert", *copy_current_cert == (*test_vector)[0]); + CHECK(*copy_current_cert == (*test_vector)[0]); // operator--(int) current_cert--; - ensure("validate iterator-- element in vector is expected cert", *current_cert-- == (*test_vector)[1]); - ensure("validate iterator-- element in vector is expected cert", *current_cert == (*test_vector)[0]); + CHECK(*current_cert-- == (*test_vector)[1]); + CHECK(*current_cert == (*test_vector)[0]); - ensure("begin iterator is equal", current_cert == test_vector->begin()); + CHECK(current_cert == test_vector->begin()); // operator++ - ensure("validate ++iterator element in vector is expected cert", *++current_cert == (*test_vector)[1]); - ensure("end of cert vector after ++iterator", ++current_cert == test_vector->end()); + CHECK(*++current_cert == (*test_vector)[1]); + REQUIRE(++current_cert == test_vector->end()); // operator-- - ensure("validate --iterator element in vector is expected cert", *--current_cert == (*test_vector)[1]); - ensure("validate 2nd --iterator element in vector is expected cert", *--current_cert == (*test_vector)[0]); + CHECK(*--current_cert == (*test_vector)[1]); + CHECK(*--current_cert == (*test_vector)[0]); test_vector->erase(test_vector->begin()); - ensure_equals("one element in store after remove", test_vector->size(), 1); + UNSCOPED_INFO("one element in store after remove"); + REQUIRE(test_vector->size() == 1); test_cert = (*test_vector)[0]->getOpenSSLX509(); - ensure("Child cert remains", !X509_cmp(test_cert, mX509ChildCert)); + UNSCOPED_INFO("Child cert remains"); + CHECK_FALSE(X509_cmp(test_cert, mX509ChildCert)); X509_free(test_cert); // validate insert test_vector->insert(test_vector->begin(), new LLBasicCertificate(mPemIntermediateCert, &mValidationDate)); test_cert = (*test_vector)[0]->getOpenSSLX509(); - ensure_equals("two elements in store after insert", test_vector->size(), 2); - ensure("validate intermediate cert was inserted at first position", !X509_cmp(test_cert, mX509IntermediateCert)); + UNSCOPED_INFO("two elements in store after insert"); + REQUIRE(test_vector->size() == 2); + UNSCOPED_INFO("validate intermediate cert was inserted at first position"); + CHECK_FALSE(X509_cmp(test_cert, mX509IntermediateCert)); X509_free(test_cert); test_cert = (*test_vector)[1]->getOpenSSLX509(); - ensure("validate child cert still there", !X509_cmp(test_cert, mX509ChildCert)); + UNSCOPED_INFO("validate child cert still there"); + CHECK_FALSE(X509_cmp(test_cert, mX509ChildCert)); X509_free(test_cert); //validate find LLSD find_info = LLSD::emptyMap(); find_info["subjectKeyIdentifier"] = "7b:1a:f9:2b:c4:b2:f6:ae:d6:f2:8e:b1:73:fb:dd:11:ca:db:f8:87"; LLBasicCertificateVector::iterator found_cert = test_vector->find(find_info); - ensure("found some cert", found_cert != test_vector->end()); + REQUIRE(found_cert != test_vector->end()); X509* found_x509 = (*found_cert).get()->getOpenSSLX509(); - ensure("child cert was found", !X509_cmp(found_x509, mX509ChildCert)); + UNSCOPED_INFO("child cert was found"); + CHECK_FALSE(X509_cmp(found_x509, mX509ChildCert)); X509_free(found_x509); find_info["subjectKeyIdentifier"] = "00:00:00:00"; // bogus current_cert =test_vector->find(find_info); - ensure("didn't find cert", current_cert == test_vector->end()); + REQUIRE(current_cert == test_vector->end()); } - // test cert store - template<> template<> - void sechandler_basic_test_object::test<6>() + TEST_CASE_METHOD(sechandler_basic_test, "LLSecHandler: a certificate store loads from and saves to its file", "[LLSecHandler]") { // validate load with nothing LLFile::remove("mycertstore.pem"); LLPointer test_store = new LLBasicCertificateStore("mycertstore.pem"); - ensure_equals("when loading with nothing, we should result in no certs in store", test_store->size(), 0); + REQUIRE(test_store->size() == 0); // validate load with empty file test_store->save(); test_store = NULL; test_store = new LLBasicCertificateStore("mycertstore.pem"); - ensure_equals("when loading with nothing, we should result in no certs in store", test_store->size(), 0); + REQUIRE(test_store->size() == 0); test_store=NULL; // instantiate a cert store from a file @@ -1138,15 +1152,17 @@ namespace tut certstorefile.close(); // validate loaded certs test_store = new LLBasicCertificateStore("mycertstore.pem"); - ensure_equals("two elements in store", test_store->size(), 2); + REQUIRE(test_store->size() == 2); // operator[] X509* test_cert = (*test_store)[0]->getOpenSSLX509(); - ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + UNSCOPED_INFO("validate first element in store is expected cert"); + CHECK_FALSE(X509_cmp(test_cert, mX509ChildCert)); X509_free(test_cert); test_cert = (*test_store)[1]->getOpenSSLX509(); - ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509TestCert)); + UNSCOPED_INFO("validate second element in store is expected cert"); + CHECK_FALSE(X509_cmp(test_cert, mX509TestCert)); X509_free(test_cert); @@ -1155,131 +1171,123 @@ namespace tut test_store->save(); test_store = NULL; test_store = new LLBasicCertificateStore("mycertstore.pem"); - ensure_equals("two elements in store after save", test_store->size(), 2); + UNSCOPED_INFO("two elements in store after save"); + REQUIRE(test_store->size() == 2); LLCertificateStore::iterator current_cert = test_store->begin(); test_cert = (*current_cert)->getOpenSSLX509(); - ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + UNSCOPED_INFO("validate first element in store is expected cert"); + CHECK_FALSE(X509_cmp(test_cert, mX509ChildCert)); current_cert++; X509_free(test_cert); test_cert = (*current_cert)->getOpenSSLX509(); - ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509TestCert)); + UNSCOPED_INFO("validate second element in store is expected cert"); + CHECK_FALSE(X509_cmp(test_cert, mX509TestCert)); X509_free(test_cert); current_cert++; - ensure("end of cert store", current_cert == test_store->end()); + REQUIRE(current_cert == test_store->end()); } - // cert name wildcard matching - template<> template<> - void sechandler_basic_test_object::test<7>() + TEST_CASE_METHOD(sechandler_basic_test, "LLSecHandler: hostnames match certificate names with wildcards and end periods", "[LLSecHandler]") { - ensure("simple name match", - _cert_hostname_wildcard_match("foo", "foo")); + CHECK(_cert_hostname_wildcard_match("foo", "foo")); - ensure("simple name match, with end period", - _cert_hostname_wildcard_match("foo.", "foo.")); + UNSCOPED_INFO("simple name match, with end period"); + CHECK(_cert_hostname_wildcard_match("foo.", "foo.")); - ensure("simple name match, with begin period", - _cert_hostname_wildcard_match(".foo", ".foo")); + UNSCOPED_INFO("simple name match, with begin period"); + CHECK(_cert_hostname_wildcard_match(".foo", ".foo")); - ensure("simple name match, with mismatched period cn", - _cert_hostname_wildcard_match("foo.", "foo")); + UNSCOPED_INFO("simple name match, with mismatched period cn"); + CHECK(_cert_hostname_wildcard_match("foo.", "foo")); - ensure("simple name match, with mismatched period hostname", - _cert_hostname_wildcard_match("foo", "foo.")); + UNSCOPED_INFO("simple name match, with mismatched period hostname"); + CHECK(_cert_hostname_wildcard_match("foo", "foo.")); - ensure("simple name match, with subdomain", - _cert_hostname_wildcard_match("foo.bar", "foo.bar")); + UNSCOPED_INFO("simple name match, with subdomain"); + CHECK(_cert_hostname_wildcard_match("foo.bar", "foo.bar")); - ensure("stutter name match", - _cert_hostname_wildcard_match("foobbbbfoo", "foo*bbbfoo")); + UNSCOPED_INFO("stutter name match"); + CHECK(_cert_hostname_wildcard_match("foobbbbfoo", "foo*bbbfoo")); - ensure("simple name match, with beginning wildcard", - _cert_hostname_wildcard_match("foobar", "*bar")); + UNSCOPED_INFO("simple name match, with beginning wildcard"); + CHECK(_cert_hostname_wildcard_match("foobar", "*bar")); - ensure("simple name match, with ending wildcard", - _cert_hostname_wildcard_match("foobar", "foo*")); + UNSCOPED_INFO("simple name match, with ending wildcard"); + CHECK(_cert_hostname_wildcard_match("foobar", "foo*")); - ensure("simple name match, with beginning null wildcard", - _cert_hostname_wildcard_match("foobar", "*foobar")); + UNSCOPED_INFO("simple name match, with beginning null wildcard"); + CHECK(_cert_hostname_wildcard_match("foobar", "*foobar")); - ensure("simple name match, with ending null wildcard", - _cert_hostname_wildcard_match("foobar", "foobar*")); + UNSCOPED_INFO("simple name match, with ending null wildcard"); + CHECK(_cert_hostname_wildcard_match("foobar", "foobar*")); - ensure("simple name match, with embedded wildcard", - _cert_hostname_wildcard_match("foobar", "f*r")); + UNSCOPED_INFO("simple name match, with embedded wildcard"); + CHECK(_cert_hostname_wildcard_match("foobar", "f*r")); - ensure("simple name match, with embedded null wildcard", - _cert_hostname_wildcard_match("foobar", "foo*bar")); + UNSCOPED_INFO("simple name match, with embedded null wildcard"); + CHECK(_cert_hostname_wildcard_match("foobar", "foo*bar")); - ensure("simple name match, with dual embedded wildcard", - _cert_hostname_wildcard_match("foobar", "f*o*ar")); + UNSCOPED_INFO("simple name match, with dual embedded wildcard"); + CHECK(_cert_hostname_wildcard_match("foobar", "f*o*ar")); - ensure("simple name mismatch", - !_cert_hostname_wildcard_match("bar", "foo")); + CHECK_FALSE(_cert_hostname_wildcard_match("bar", "foo")); - ensure("simple name mismatch, with end period", - !_cert_hostname_wildcard_match("foobar.", "foo.")); + UNSCOPED_INFO("simple name mismatch, with end period"); + CHECK_FALSE(_cert_hostname_wildcard_match("foobar.", "foo.")); - ensure("simple name mismatch, with begin period", - !_cert_hostname_wildcard_match(".foobar", ".foo")); + UNSCOPED_INFO("simple name mismatch, with begin period"); + CHECK_FALSE(_cert_hostname_wildcard_match(".foobar", ".foo")); - ensure("simple name mismatch, with subdomain", - !_cert_hostname_wildcard_match("foobar.bar", "foo.bar")); + UNSCOPED_INFO("simple name mismatch, with subdomain"); + CHECK_FALSE(_cert_hostname_wildcard_match("foobar.bar", "foo.bar")); - ensure("simple name mismatch, with beginning wildcard", - !_cert_hostname_wildcard_match("foobara", "*bar")); + UNSCOPED_INFO("simple name mismatch, with beginning wildcard"); + CHECK_FALSE(_cert_hostname_wildcard_match("foobara", "*bar")); - ensure("simple name mismatch, with ending wildcard", - !_cert_hostname_wildcard_match("oobar", "foo*")); + UNSCOPED_INFO("simple name mismatch, with ending wildcard"); + CHECK_FALSE(_cert_hostname_wildcard_match("oobar", "foo*")); - ensure("simple name mismatch, with embedded wildcard", - !_cert_hostname_wildcard_match("oobar", "f*r")); + UNSCOPED_INFO("simple name mismatch, with embedded wildcard"); + CHECK_FALSE(_cert_hostname_wildcard_match("oobar", "f*r")); - ensure("simple name mismatch, with dual embedded wildcard", - !_cert_hostname_wildcard_match("foobar", "f*d*ar")); + UNSCOPED_INFO("simple name mismatch, with dual embedded wildcard"); + CHECK_FALSE(_cert_hostname_wildcard_match("foobar", "f*d*ar")); - ensure("simple wildcard", - _cert_hostname_wildcard_match("foobar", "*")); + UNSCOPED_INFO("simple wildcard"); + CHECK(_cert_hostname_wildcard_match("foobar", "*")); - ensure("long domain", - _cert_hostname_wildcard_match("foo.bar.com", "foo.bar.com")); + UNSCOPED_INFO("long domain"); + CHECK(_cert_hostname_wildcard_match("foo.bar.com", "foo.bar.com")); - ensure("long domain with multiple wildcards", - _cert_hostname_wildcard_match("foo.bar.com", "*.b*r.com")); + UNSCOPED_INFO("long domain with multiple wildcards"); + CHECK(_cert_hostname_wildcard_match("foo.bar.com", "*.b*r.com")); - ensure("end periods", - _cert_hostname_wildcard_match("foo.bar.com.", "*.b*r.com.")); + UNSCOPED_INFO("end periods"); + CHECK(_cert_hostname_wildcard_match("foo.bar.com.", "*.b*r.com.")); - ensure("match end period", - _cert_hostname_wildcard_match("foo.bar.com.", "*.b*r.com")); + UNSCOPED_INFO("match end period"); + CHECK(_cert_hostname_wildcard_match("foo.bar.com.", "*.b*r.com")); - ensure("match end period2", - _cert_hostname_wildcard_match("foo.bar.com", "*.b*r.com.")); + UNSCOPED_INFO("match end period2"); + CHECK(_cert_hostname_wildcard_match("foo.bar.com", "*.b*r.com.")); - ensure("wildcard mismatch", - !_cert_hostname_wildcard_match("bar.com", "*.bar.com")); + CHECK_FALSE(_cert_hostname_wildcard_match("bar.com", "*.bar.com")); - ensure("wildcard match", - _cert_hostname_wildcard_match("foo.bar.com", "*.bar.com")); + CHECK(_cert_hostname_wildcard_match("foo.bar.com", "*.bar.com")); - ensure("wildcard match", - _cert_hostname_wildcard_match("foo.foo.bar.com", "*.bar.com")); + CHECK(_cert_hostname_wildcard_match("foo.foo.bar.com", "*.bar.com")); - ensure("wildcard match", - _cert_hostname_wildcard_match("foo.foo.bar.com", "*.*.com")); + CHECK(_cert_hostname_wildcard_match("foo.foo.bar.com", "*.*.com")); - ensure("wildcard mismatch", - !_cert_hostname_wildcard_match("foo.foo.bar.com", "*.foo.com")); + CHECK_FALSE(_cert_hostname_wildcard_match("foo.foo.bar.com", "*.foo.com")); } - // test cert chain - template<> template<> - void sechandler_basic_test_object::test<8>() + TEST_CASE_METHOD(sechandler_basic_test, "LLSecHandler: a certificate chain follows issuers and drops unrelated certs", "[LLSecHandler]") { // validate create from empty chain LLPointer test_chain = new LLBasicCertificateChain(NULL); - ensure_equals("when loading with nothing, we should result in no certs in chain", test_chain->size(), 0); + REQUIRE(test_chain->size() == 0); // Single cert in the chain. X509_STORE_CTX *test_store = X509_STORE_CTX_new(); @@ -1287,9 +1295,10 @@ namespace tut X509_STORE_CTX_set0_untrusted(test_store, sk_X509_new_null()); test_chain = new LLBasicCertificateChain(test_store); X509_STORE_CTX_free(test_store); - ensure_equals("two elements in store [1]", test_chain->size(), 1); + REQUIRE(test_chain->size() == 1); X509* test_cert = (*test_chain)[0]->getOpenSSLX509(); - ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + UNSCOPED_INFO("validate first element in store is expected cert"); + CHECK_FALSE(X509_cmp(test_cert, mX509ChildCert)); X509_free(test_cert); // cert + CA @@ -1300,12 +1309,14 @@ namespace tut sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509IntermediateCert); test_chain = new LLBasicCertificateChain(test_store); X509_STORE_CTX_free(test_store); - ensure_equals("two elements in store [2]", test_chain->size(), 2); + REQUIRE(test_chain->size() == 2); test_cert = (*test_chain)[0]->getOpenSSLX509(); - ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + UNSCOPED_INFO("validate first element in store is expected cert"); + CHECK_FALSE(X509_cmp(test_cert, mX509ChildCert)); X509_free(test_cert); test_cert = (*test_chain)[1]->getOpenSSLX509(); - ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509IntermediateCert)); + UNSCOPED_INFO("validate second element in store is expected cert"); + CHECK_FALSE(X509_cmp(test_cert, mX509IntermediateCert)); X509_free(test_cert); // cert + nonrelated @@ -1316,9 +1327,10 @@ namespace tut sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509TestCert); test_chain = new LLBasicCertificateChain(test_store); X509_STORE_CTX_free(test_store); - ensure_equals("two elements in store [3]", test_chain->size(), 1); + REQUIRE(test_chain->size() == 1); test_cert = (*test_chain)[0]->getOpenSSLX509(); - ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + UNSCOPED_INFO("validate first element in store is expected cert"); + CHECK_FALSE(X509_cmp(test_cert, mX509ChildCert)); X509_free(test_cert); // cert + CA + nonrelated @@ -1329,12 +1341,14 @@ namespace tut sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509TestCert); test_chain = new LLBasicCertificateChain(test_store); X509_STORE_CTX_free(test_store); - ensure_equals("two elements in store [4]", test_chain->size(), 2); + REQUIRE(test_chain->size() == 2); test_cert = (*test_chain)[0]->getOpenSSLX509(); - ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + UNSCOPED_INFO("validate first element in store is expected cert"); + CHECK_FALSE(X509_cmp(test_cert, mX509ChildCert)); X509_free(test_cert); test_cert = (*test_chain)[1]->getOpenSSLX509(); - ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509IntermediateCert)); + UNSCOPED_INFO("validate second element in store is expected cert"); + CHECK_FALSE(X509_cmp(test_cert, mX509IntermediateCert)); X509_free(test_cert); // cert + intermediate + CA @@ -1345,22 +1359,23 @@ namespace tut sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509RootCert); test_chain = new LLBasicCertificateChain(test_store); X509_STORE_CTX_free(test_store); - ensure_equals("three elements in store", test_chain->size(), 3); + REQUIRE(test_chain->size() == 3); test_cert = (*test_chain)[0]->getOpenSSLX509(); - ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + UNSCOPED_INFO("validate first element in store is expected cert"); + CHECK_FALSE(X509_cmp(test_cert, mX509ChildCert)); X509_free(test_cert); test_cert = (*test_chain)[1]->getOpenSSLX509(); - ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509IntermediateCert)); + UNSCOPED_INFO("validate second element in store is expected cert"); + CHECK_FALSE(X509_cmp(test_cert, mX509IntermediateCert)); X509_free(test_cert); test_cert = (*test_chain)[2]->getOpenSSLX509(); - ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509RootCert)); + UNSCOPED_INFO("validate third element in store is expected cert"); + CHECK_FALSE(X509_cmp(test_cert, mX509RootCert)); X509_free(test_cert); } - // test cert validation - template<> template<> - void sechandler_basic_test_object::test<9>() + TEST_CASE_METHOD(sechandler_basic_test, "LLSecHandler: validation needs a trusted CA and a valid date and SSL key usage", "[LLSecHandler]") { // start with a trusted store with our known root cert LLFile::remove("mycertstore.pem"); @@ -1394,13 +1409,13 @@ namespace tut // Validate with child cert but no parent, and no parent in CA store test_store = new LLBasicCertificateStore("mycertstore.pem"); - ensure_throws("no CA, with only a child cert", - LLCertValidationTrustException, - (*test_chain)[0], - test_store->validate, - VALIDATION_POLICY_TRUSTED, - test_chain, - validation_params); + require_throws_with_cert("no CA, with only a child cert", + LLCertValidationTrustException, + (*test_chain)[0], + test_store->validate, + VALIDATION_POLICY_TRUSTED, + test_chain, + validation_params); // validate without the trust flag. @@ -1410,13 +1425,13 @@ namespace tut test_store = new LLBasicCertificateStore("mycertstore.pem"); // append the intermediate cert test_chain->add(new LLBasicCertificate(mX509IntermediateCert, &mValidationDate)); - ensure_throws("no CA, with child and intermediate certs", - LLCertValidationTrustException, - (*test_chain)[1], - test_store->validate, - VALIDATION_POLICY_TRUSTED | VALIDATION_POLICY_TRUSTED, - test_chain, - validation_params); + require_throws_with_cert("no CA, with child and intermediate certs", + LLCertValidationTrustException, + (*test_chain)[1], + test_store->validate, + VALIDATION_POLICY_TRUSTED | VALIDATION_POLICY_TRUSTED, + test_chain, + validation_params); // validate without the trust flag test_store->validate(0, test_chain, validation_params); @@ -1434,24 +1449,24 @@ namespace tut validation_params[CERT_VALIDATION_DATE] = LLDate(child_info[CERT_VALID_FROM].asDate().secondsSinceEpoch() - 1.0); // test not yet valid - ensure_throws("Child cert not yet valid" , - LLCertValidationExpirationException, - (*test_chain)[0], - test_store->validate, - VALIDATION_POLICY_TIME | VALIDATION_POLICY_TRUSTED, - test_chain, - validation_params); + require_throws_with_cert("Child cert not yet valid" , + LLCertValidationExpirationException, + (*test_chain)[0], + test_store->validate, + VALIDATION_POLICY_TIME | VALIDATION_POLICY_TRUSTED, + test_chain, + validation_params); validation_params = LLSD::emptyMap(); validation_params[CERT_VALIDATION_DATE] = LLDate(child_info[CERT_VALID_TO].asDate().secondsSinceEpoch() + 1.0); // test cert expired - ensure_throws("Child cert expired", - LLCertValidationExpirationException, - (*test_chain)[0], - test_store->validate, - VALIDATION_POLICY_TIME | VALIDATION_POLICY_TRUSTED, - test_chain, - validation_params); + require_throws_with_cert("Child cert expired", + LLCertValidationExpirationException, + (*test_chain)[0], + test_store->validate, + VALIDATION_POLICY_TIME | VALIDATION_POLICY_TRUSTED, + test_chain, + validation_params); // test SSL KU // validate basic trust for a chain containing child and intermediate. @@ -1465,13 +1480,13 @@ namespace tut test_chain->add(new LLBasicCertificate(mX509TestCert, &mValidationDate)); test_store = new LLBasicCertificateStore("mycertstore.pem"); - ensure_throws("Cert doesn't have ku", - LLCertKeyUsageValidationException, - (*test_chain)[0], - test_store->validate, - VALIDATION_POLICY_SSL_KU | VALIDATION_POLICY_TRUSTED, - test_chain, - validation_params); + require_throws_with_cert("Cert doesn't have ku", + LLCertKeyUsageValidationException, + (*test_chain)[0], + test_store->validate, + VALIDATION_POLICY_SSL_KU | VALIDATION_POLICY_TRUSTED, + test_chain, + validation_params); test_store->validate(0, test_chain, validation_params); } diff --git a/indra/newview/tests/llslurl_test.cpp b/indra/newview/tests/llslurl_test.cpp index 2e0f448842..e53340a1fc 100644 --- a/indra/newview/tests/llslurl_test.cpp +++ b/indra/newview/tests/llslurl_test.cpp @@ -27,7 +27,7 @@ */ #include "../llviewerprecompiledheaders.h" #include "../llviewernetwork.h" -#include "../test/lltut.h" +#include #include "../llslurl.h" #include "../../llxml/llcontrol.h" #include "llsdserialize.h" @@ -176,7 +176,7 @@ static const char *gSampleGridFile = // ------------------------------------------------------------------------------------------- // TUT // ------------------------------------------------------------------------------------------- -namespace tut +namespace { // Test wrapper declaration : wrapping nothing for the moment struct slurlTest @@ -192,17 +192,10 @@ namespace tut } }; - // Tut templating thingamagic: test group, object and test instance - typedef test_group slurlTestFactory; - typedef slurlTestFactory::object slurlTestObject; - tut::slurlTestFactory tut_slurl("LLSlurl"); - // --------------------------------------------------------------------------------------- // Test functions // --------------------------------------------------------------------------------------- - // construction from slurl string - template<> template<> - void slurlTestObject::test<1>() + TEST_CASE_METHOD(slurlTest, "LLSlurl: construction from each form of SLURL string", "[LLSlurl]") { llofstream gridfile(TEST_FILENAME); gridfile << gSampleGridFile; @@ -213,119 +206,168 @@ namespace tut LLGridManager::getInstance()->setGridChoice("util.agni.lindenlab.com"); LLSLURL slurl = LLSLURL(""); - ensure_equals("null slurl", (int)slurl.getType(), LLSLURL::LAST_LOCATION); + UNSCOPED_INFO("null slurl"); + CHECK((int)slurl.getType() == LLSLURL::LAST_LOCATION); slurl = LLSLURL("https://slurl.com/secondlife/myregion"); - ensure_equals("slurl.com slurl, region only - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("slurl.com slurl, region only", slurl.getSLURLString(), - "https://maps.secondlife.com/secondlife/myregion/128/128/0"); + { + INFO("slurl.com slurl, region only"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getSLURLString() == + "https://maps.secondlife.com/secondlife/myregion/128/128/0"); + } slurl = LLSLURL("https://maps.secondlife.com/secondlife/myregion/1/2/3"); - ensure_equals("maps.secondlife.com slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("maps.secondlife.com slurl, region + coords", slurl.getSLURLString(), - "https://maps.secondlife.com/secondlife/myregion/1/2/3"); + { + INFO("maps.secondlife.com slurl, region + coords"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getSLURLString() == + "https://maps.secondlife.com/secondlife/myregion/1/2/3"); + } slurl = LLSLURL("secondlife://"); - ensure_equals("secondlife: slurl, empty - type", slurl.getType(), LLSLURL::EMPTY); + UNSCOPED_INFO("secondlife: slurl, empty"); + CHECK(slurl.getType() == LLSLURL::EMPTY); slurl = LLSLURL("secondlife:///"); - ensure_equals("secondlife: slurl, root - type", slurl.getType(), LLSLURL::EMPTY); + UNSCOPED_INFO("secondlife: slurl, root"); + CHECK(slurl.getType() == LLSLURL::EMPTY); slurl = LLSLURL("secondlife://myregion"); - ensure_equals("secondlife: slurl, region only - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("secondlife: slurl, region only", slurl.getSLURLString(), - "https://maps.secondlife.com/secondlife/myregion/128/128/0"); + { + INFO("secondlife: slurl, region only"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getSLURLString() == + "https://maps.secondlife.com/secondlife/myregion/128/128/0"); + } slurl = LLSLURL("secondlife://myregion/1/2/3"); - ensure_equals("secondlife: slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("secondlife slurl, region + coords", slurl.getSLURLString(), - "https://maps.secondlife.com/secondlife/myregion/1/2/3"); + { + INFO("secondlife: slurl, region + coords"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getSLURLString() == + "https://maps.secondlife.com/secondlife/myregion/1/2/3"); + } slurl = LLSLURL("/myregion"); - ensure_equals("/region slurl, region- type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("/region slurl, region ", slurl.getSLURLString(), - "https://maps.secondlife.com/secondlife/myregion/128/128/0"); + { + INFO("/region slurl, region only"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getSLURLString() == + "https://maps.secondlife.com/secondlife/myregion/128/128/0"); + } slurl = LLSLURL("/myregion/1/2/3"); - ensure_equals("/: slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("/ slurl, region + coords", slurl.getSLURLString(), - "https://maps.secondlife.com/secondlife/myregion/1/2/3"); + { + INFO("/region slurl, region + coords"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getSLURLString() == + "https://maps.secondlife.com/secondlife/myregion/1/2/3"); + } slurl = LLSLURL("my region/1/2/3"); - ensure_equals(" slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals(" slurl, region + coords", slurl.getSLURLString(), - "https://maps.secondlife.com/secondlife/my%20region/1/2/3"); + { + INFO("bare slurl, region with a space + coords"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getSLURLString() == + "https://maps.secondlife.com/secondlife/my%20region/1/2/3"); + } LLGridManager::getInstance()->setGridChoice("my.grid.com"); slurl = LLSLURL("https://my.grid.com/region/my%20region/1/2/3"); - ensure_equals("grid slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("grid slurl, region + coords", slurl.getSLURLString(), - "https://my.grid.com/region/my%20region/1/2/3"); + { + INFO("grid slurl, region + coords"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getSLURLString() == + "https://my.grid.com/region/my%20region/1/2/3"); + } slurl = LLSLURL("https://my.grid.com/region/my region"); - ensure_equals("grid slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("grid slurl, region + coords", slurl.getSLURLString(), - "https://my.grid.com/region/my%20region/128/128/0"); + { + INFO("grid slurl, region only with an unescaped space"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getSLURLString() == + "https://my.grid.com/region/my%20region/128/128/0"); + } LLGridManager::getInstance()->setGridChoice("foo.bar.com"); slurl = LLSLURL("/myregion/1/2/3"); - ensure_equals("/: slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("/ slurl, region + coords", slurl.getSLURLString(), - "https://foo.bar.com/region/myregion/1/2/3"); + { + INFO("/region slurl on foo.bar.com, region + coords"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getSLURLString() == + "https://foo.bar.com/region/myregion/1/2/3"); + } slurl = LLSLURL("myregion/1/2/3"); - ensure_equals(": slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals(" slurl, region + coords", slurl.getSLURLString(), - "https://foo.bar.com/region/myregion/1/2/3"); + { + INFO("bare slurl on foo.bar.com, region + coords"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getSLURLString() == + "https://foo.bar.com/region/myregion/1/2/3"); + } slurl = LLSLURL(LLSLURL::SIM_LOCATION_HOME); - ensure_equals("home", slurl.getType(), LLSLURL::HOME_LOCATION); + CHECK(slurl.getType() == LLSLURL::HOME_LOCATION); slurl = LLSLURL(LLSLURL::SIM_LOCATION_LAST); - ensure_equals("last", slurl.getType(), LLSLURL::LAST_LOCATION); + CHECK(slurl.getType() == LLSLURL::LAST_LOCATION); slurl = LLSLURL("secondlife:///app/foo/bar?12345"); - ensure_equals("app", slurl.getType(), LLSLURL::APP); - ensure_equals("appcmd", slurl.getAppCmd(), "foo"); - ensure_equals("apppath", slurl.getAppPath().size(), 1); - ensure_equals("apppath2", slurl.getAppPath()[0].asString(), "bar"); - ensure_equals("appquery", slurl.getAppQuery(), "12345"); - ensure_equals("grid1", slurl.getGrid(), "FooBar"); + { + INFO("app slurl naming no grid"); + CHECK(slurl.getType() == LLSLURL::APP); + CHECK(slurl.getAppCmd() == "foo"); + REQUIRE(slurl.getAppPath().size() == 1); + CHECK(slurl.getAppPath()[0].asString() == "bar"); + CHECK(slurl.getAppQuery() == "12345"); + CHECK(slurl.getGrid() == "FooBar"); + } slurl = LLSLURL("secondlife://Aditi/app/foo/bar?12345"); - ensure_equals("app", slurl.getType(), LLSLURL::APP); - ensure_equals("appcmd", slurl.getAppCmd(), "foo"); - ensure_equals("apppath", slurl.getAppPath().size(), 1); - ensure_equals("apppath2", slurl.getAppPath()[0].asString(), "bar"); - ensure_equals("appquery", slurl.getAppQuery(), "12345"); - ensure_equals("grid2", slurl.getGrid(), "Aditi"); + { + INFO("app slurl naming Aditi"); + CHECK(slurl.getType() == LLSLURL::APP); + CHECK(slurl.getAppCmd() == "foo"); + REQUIRE(slurl.getAppPath().size() == 1); + CHECK(slurl.getAppPath()[0].asString() == "bar"); + CHECK(slurl.getAppQuery() == "12345"); + CHECK(slurl.getGrid() == "Aditi"); + } LLGridManager::getInstance()->setGridChoice("foo.bar.com"); slurl = LLSLURL("secondlife:///secondlife/myregion/1/2/3"); - ensure_equals("/: slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("location", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("region" , "myregion", slurl.getRegion()); - ensure_equals("grid3", slurl.getGrid(), "util.agni.lindenlab.com"); + { + INFO("secondlife:///secondlife/ slurl naming no grid"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK("myregion" == slurl.getRegion()); + CHECK(slurl.getGrid() == "util.agni.lindenlab.com"); + } slurl = LLSLURL("secondlife://Aditi/secondlife/myregion/1/2/3"); - ensure_equals("/: slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("location", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("region" , "myregion", slurl.getRegion()); - ensure_equals("grid4", slurl.getGrid(), "Aditi" ); + { + INFO("secondlife://Aditi/secondlife/ slurl"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK("myregion" == slurl.getRegion()); + CHECK(slurl.getGrid() == "Aditi"); + } LLGridManager::getInstance()->setGridChoice("my.grid.com"); slurl = LLSLURL("https://my.grid.com/app/foo/bar?12345"); - ensure_equals("app", slurl.getType(), LLSLURL::APP); - ensure_equals("appcmd", slurl.getAppCmd(), "foo"); - ensure_equals("apppath", slurl.getAppPath().size(), 1); - ensure_equals("apppath2", slurl.getAppPath()[0].asString(), "bar"); - ensure_equals("appquery", slurl.getAppQuery(), "12345"); + { + INFO("app slurl on my.grid.com"); + CHECK(slurl.getType() == LLSLURL::APP); + CHECK(slurl.getAppCmd() == "foo"); + REQUIRE(slurl.getAppPath().size() == 1); + CHECK(slurl.getAppPath()[0].asString() == "bar"); + CHECK(slurl.getAppQuery() == "12345"); + } } - // construction from grid/region/vector combos - template<> template<> - void slurlTestObject::test<2>() + TEST_CASE_METHOD(slurlTest, "LLSlurl: construction from grid/region/vector combos", "[LLSlurl]") { llofstream gridfile(TEST_FILENAME); gridfile << gSampleGridFile; @@ -334,31 +376,42 @@ namespace tut LLGridManager::getInstance()->initialize(TEST_FILENAME); LLSLURL slurl = LLSLURL("my.grid.com", "my region"); - ensure_equals("grid/region - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("grid/region", slurl.getSLURLString(), - "https://my.grid.com/region/my%20region/128/128/0"); + { + INFO("grid/region"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getSLURLString() == + "https://my.grid.com/region/my%20region/128/128/0"); + } slurl = LLSLURL("my.grid.com", "my region", LLVector3(1,2,3)); - ensure_equals("grid/region/vector - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals(" grid/region/vector", slurl.getSLURLString(), - "https://my.grid.com/region/my%20region/1/2/3"); + { + INFO("grid/region/vector"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getSLURLString() == + "https://my.grid.com/region/my%20region/1/2/3"); + } LLGridManager::getInstance()->setGridChoice("util.agni.lindenlab.com"); slurl = LLSLURL("my region", LLVector3(1,2,3)); - ensure_equals("default grid/region/vector - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals(" default grid/region/vector", slurl.getSLURLString(), - "https://maps.secondlife.com/secondlife/my%20region/1/2/3"); + { + INFO("region/vector on the agni grid"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getSLURLString() == + "https://maps.secondlife.com/secondlife/my%20region/1/2/3"); + } LLGridManager::getInstance()->setGridChoice("MyGrid"); slurl = LLSLURL("my region", LLVector3(1,2,3)); - ensure_equals("default grid/region/vector - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals(" default grid/region/vector", slurl.getSLURLString(), - "https://my.grid.com/region/my%20region/1/2/3"); + { + INFO("region/vector on the grid chosen by its login id"); + CHECK(slurl.getType() == LLSLURL::LOCATION); + CHECK(slurl.getSLURLString() == + "https://my.grid.com/region/my%20region/1/2/3"); + } } - // Accessors - template<> template<> - void slurlTestObject::test<3>() + + TEST_CASE_METHOD(slurlTest, "LLSlurl: the accessors give the login and location strings and the parts", "[LLSlurl]") { llofstream gridfile(TEST_FILENAME); gridfile << gSampleGridFile; @@ -368,11 +421,11 @@ namespace tut LLGridManager::getInstance()->setGridChoice("my.grid.com"); LLSLURL slurl = LLSLURL("https://my.grid.com/region/my%20region/1/2/3"); - ensure_equals("login string", slurl.getLoginString(), "uri:my region&1&2&3"); - ensure_equals("location string", slurl.getLocationString(), "my region/1/2/3"); - ensure_equals("grid", slurl.getGrid(), "my.grid.com"); - ensure_equals("region", slurl.getRegion(), "my region"); - ensure_equals("position", slurl.getPosition(), LLVector3(1, 2, 3)); + CHECK(slurl.getLoginString() == "uri:my region&1&2&3"); + CHECK(slurl.getLocationString() == "my region/1/2/3"); + CHECK(slurl.getGrid() == "my.grid.com"); + CHECK(slurl.getRegion() == "my region"); + CHECK(slurl.getPosition() == LLVector3(1, 2, 3)); } } diff --git a/indra/newview/tests/lltextureinfo_test.cpp b/indra/newview/tests/lltextureinfo_test.cpp index 2dcab18c14..3bbc085b68 100644 --- a/indra/newview/tests/lltextureinfo_test.cpp +++ b/indra/newview/tests/lltextureinfo_test.cpp @@ -40,7 +40,7 @@ #include "../llvocache.h" // Tut header -#include "../test/lltut.h" +#include // ------------------------------------------------------------------------------------------- // Stubbing: Declarations required to link and run the class being tested @@ -89,7 +89,7 @@ LLVOCache::~LLVOCache() {} // TUT // ------------------------------------------------------------------------------------------- -namespace tut +namespace { // Test wrapper declarations struct textureinfo_test @@ -103,12 +103,6 @@ namespace tut } }; - // Tut templating thingamagic: test group, object and test instance - typedef test_group textureinfo_t; - typedef textureinfo_t::object textureinfo_object_t; - tut::textureinfo_t tut_textureinfo("LLTectureInfo"); - - // --------------------------------------------------------------------------------------- // Test functions // Notes: @@ -121,30 +115,23 @@ namespace tut // Test the LLTextureInfo // --------------------------------------------------------------------------------------- - - // Test instantiation - template<> template<> - void textureinfo_object_t::test<1>() + TEST_CASE_METHOD(textureinfo_test, "LLTectureInfo: an LLTextureInfo is made with logging on", "[LLTectureInfo]") { LLTextureInfo tex_info; tex_info.setLogging(true); - ensure("have we crashed?", true); + CHECK(true); } - // Check lltextureinfo does not contain UUIDs we haven't added - template<> template<> - void textureinfo_object_t::test<2>() + TEST_CASE_METHOD(textureinfo_test, "LLTectureInfo: has() is false for a UUID never added", "[LLTectureInfo]") { LLTextureInfo tex_info; tex_info.setLogging(true); LLUUID nonExistant("3a0efa3b-84dc-4e17-9b8c-79ea028850c1"); - ensure(!tex_info.has(nonExistant)); + CHECK_FALSE(tex_info.has(nonExistant)); } - // Check we can add a request time for a texture - template<> template<> - void textureinfo_object_t::test<3>() + TEST_CASE_METHOD(textureinfo_test, "LLTectureInfo: a request start time set is read back", "[LLTectureInfo]") { LLTextureInfo tex_info; tex_info.setLogging(true); @@ -152,34 +139,28 @@ namespace tut LLUUID id("10e65d70-46fd-429f-841a-bf698e9424d3"); tex_info.setRequestStartTime(id, 200); - ensure_equals(tex_info.getRequestStartTime(id), 200); + CHECK(tex_info.getRequestStartTime(id) == 200); } - // Check time for non-existant texture - template<> template<> - void textureinfo_object_t::test<4>() + TEST_CASE_METHOD(textureinfo_test, "LLTectureInfo: an unknown texture's request start time is 0", "[LLTectureInfo]") { LLTextureInfo tex_info; tex_info.setLogging(true); LLUUID nonExistant("3a0efa3b-84dc-4e17-9b8c-79ea028850c1"); - ensure_equals(tex_info.getRequestStartTime(nonExistant), 0); + CHECK(tex_info.getRequestStartTime(nonExistant) == 0); } - // Check download complete time for non existant texture - template<> template<> - void textureinfo_object_t::test<5>() + TEST_CASE_METHOD(textureinfo_test, "LLTectureInfo: an unknown texture's request complete time is 0", "[LLTectureInfo]") { LLTextureInfo tex_info; tex_info.setLogging(true); LLUUID nonExistant("3a0efa3b-84dc-4e17-9b8c-79ea028850c1"); - ensure_equals(tex_info.getRequestCompleteTime(nonExistant), 0); + CHECK(tex_info.getRequestCompleteTime(nonExistant) == 0); } - // requested size is passed in correctly - template<> template<> - void textureinfo_object_t::test<6>() + TEST_CASE_METHOD(textureinfo_test, "LLTectureInfo: a request size set is read back", "[LLTectureInfo]") { LLTextureInfo tex_info; tex_info.setLogging(true); @@ -187,12 +168,10 @@ namespace tut LLUUID id("10e65d70-46fd-429f-841a-bf698e9424d3"); tex_info.setRequestSize(id, 600); - ensure_equals(tex_info.getRequestSize(id), 600); + CHECK(tex_info.getRequestSize(id) == 600); } - // transport type is recorded correctly (http) - template<> template<> - void textureinfo_object_t::test<7>() + TEST_CASE_METHOD(textureinfo_test, "LLTectureInfo: an HTTP request type is recorded", "[LLTectureInfo]") { LLTextureInfo tex_info; tex_info.setLogging(true); @@ -200,12 +179,10 @@ namespace tut LLUUID id("10e65d70-46fd-429f-841a-bf698e9424d3"); tex_info.setRequestType(id, LLTextureInfoDetails::REQUEST_TYPE_HTTP); - ensure_equals(tex_info.getRequestType(id), LLTextureInfoDetails::REQUEST_TYPE_HTTP); + CHECK(tex_info.getRequestType(id) == LLTextureInfoDetails::REQUEST_TYPE_HTTP); } - // transport type is recorded correctly (udp) - template<> template<> - void textureinfo_object_t::test<8>() + TEST_CASE_METHOD(textureinfo_test, "LLTectureInfo: a UDP request type is recorded", "[LLTectureInfo]") { LLTextureInfo tex_info; tex_info.setLogging(true); @@ -213,12 +190,10 @@ namespace tut LLUUID id("10e65d70-46fd-429f-841a-bf698e9424d3"); tex_info.setRequestType(id, LLTextureInfoDetails::REQUEST_TYPE_UDP); - ensure_equals(tex_info.getRequestType(id), LLTextureInfoDetails::REQUEST_TYPE_UDP); + CHECK(tex_info.getRequestType(id) == LLTextureInfoDetails::REQUEST_TYPE_UDP); } - // request offset is recorded correctly - template<> template<> - void textureinfo_object_t::test<9>() + TEST_CASE_METHOD(textureinfo_test, "LLTectureInfo: a request offset set is read back", "[LLTectureInfo]") { LLTextureInfo tex_info; tex_info.setLogging(true); @@ -226,14 +201,12 @@ namespace tut LLUUID id("10e65d70-46fd-429f-841a-bf698e9424d3"); tex_info.setRequestOffset(id, 1234); - ensure_equals(tex_info.getRequestOffset(id), 1234); + CHECK(tex_info.getRequestOffset(id) == 1234); } - // ask for averages gives us correct figure - template<> template<> - void textureinfo_object_t::test<10>() + TEST_CASE_METHOD(textureinfo_test, "LLTectureInfo: getAverages gives the bit rate and bytes and transport", "[LLTectureInfo]") { - skip("Test skipped due to use of LLTrace stats failing in test"); + SKIP("Test skipped due to use of LLTrace stats failing in test"); LLTextureInfo tex_info; tex_info.setLogging(true); @@ -265,14 +238,12 @@ namespace tut S32 totalBytes = requestSizeOne + requestSizeTwo; LLSD results = tex_info.getAverages(); - ensure_equals("is average bits per second correct", results["bits_per_second"].asInteger(), averageBitRate); - ensure_equals("is total bytes is correct", results["bytes_downloaded"].asInteger(), totalBytes); - ensure_equals("is transport correct", results["transport"].asString(), std::string("HTTP")); + CHECK(results["bits_per_second"].asInteger() == averageBitRate); + CHECK(results["bytes_downloaded"].asInteger() == totalBytes); + CHECK(results["transport"].asString() == std::string("HTTP")); } - // make sure averages cleared when reset is called - template<> template<> - void textureinfo_object_t::test<11>() + TEST_CASE_METHOD(textureinfo_test, "LLTectureInfo: resetTextureStatistics clears the averages", "[LLTectureInfo]") { LLTextureInfo tex_info; tex_info.setLogging(true); @@ -289,14 +260,12 @@ namespace tut tex_info.getAverages(); tex_info.resetTextureStatistics(); LLSD results = tex_info.getAverages(); - ensure_equals("is average bits per second correct", results["bits_per_second"].asInteger(), 0); - ensure_equals("is total bytes is correct", results["bytes_downloaded"].asInteger(), 0); - ensure_equals("is transport correct", results["transport"].asString(), std::string("NONE")); + CHECK(results["bits_per_second"].asInteger() == 0); + CHECK(results["bytes_downloaded"].asInteger() == 0); + CHECK(results["transport"].asString() == std::string("NONE")); } - // make sure map item removed when expired - template<> template<> - void textureinfo_object_t::test<12>() + TEST_CASE_METHOD(textureinfo_test, "LLTectureInfo: a request's map entry goes once it completes", "[LLTectureInfo]") { LLTextureInfo tex_info; tex_info.setLogging(true); @@ -309,11 +278,11 @@ namespace tut tex_info.setRequestSize(id1, requestSizeOne); tex_info.setRequestType(id1, LLTextureInfoDetails::REQUEST_TYPE_HTTP); - ensure_equals("map item created", tex_info.getTextureInfoMapSize(), 1); + CHECK(tex_info.getTextureInfoMapSize() == 1); tex_info.setRequestCompleteTimeAndLog(id1, requestEndTimeOne); - ensure_equals("map item removed when consumed", tex_info.getTextureInfoMapSize(), 0); + CHECK(tex_info.getTextureInfoMapSize() == 0); } } diff --git a/indra/newview/tests/llversioninfo_test.cpp b/indra/newview/tests/llversioninfo_test.cpp index 2e217c7a19..6bbbe6e0d7 100644 --- a/indra/newview/tests/llversioninfo_test.cpp +++ b/indra/newview/tests/llversioninfo_test.cpp @@ -25,7 +25,7 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../llversioninfo.h" @@ -38,7 +38,7 @@ // need to turn it into a quoted string. LL_TO_STRING() does that. #define ll_viewer_channel LL_TO_STRING(LL_VIEWER_CHANNEL) -namespace tut +namespace { struct versioninfo { @@ -77,39 +77,18 @@ namespace tut std::string mResetVersionAndChannel; }; - typedef test_group versioninfo_t; - typedef versioninfo_t::object versioninfo_object_t; - tut::versioninfo_t tut_versioninfo("LLVersionInfo"); - - template<> template<> - void versioninfo_object_t::test<1>() + TEST_CASE_METHOD(versioninfo, "LLVersionInfo: the version and channel are the build's until resetChannel", "[LLVersionInfo]") { std::cout << "What we parsed from CMake: " << LL_VIEWER_VERSION_BUILD << std::endl; std::cout << "What we get from llversioninfo: " << LLVersionInfo::instance().getBuild() << std::endl; - ensure_equals("Major version", - LLVersionInfo::instance().getMajor(), - LL_VIEWER_VERSION_MAJOR); - ensure_equals("Minor version", - LLVersionInfo::instance().getMinor(), - LL_VIEWER_VERSION_MINOR); - ensure_equals("Patch version", - LLVersionInfo::instance().getPatch(), - LL_VIEWER_VERSION_PATCH); - ensure_equals("Build version", - LLVersionInfo::instance().getBuild(), - LL_VIEWER_VERSION_BUILD); - ensure_equals("Channel version", - LLVersionInfo::instance().getChannel(), - ll_viewer_channel); - ensure_equals("Version String", - LLVersionInfo::instance().getVersion(), - mVersion); - ensure_equals("Short Version String", - LLVersionInfo::instance().getShortVersion(), - mShortVersion); - ensure_equals("Version and channel String", - LLVersionInfo::instance().getChannelAndVersion(), - mVersionAndChannel); + CHECK(LLVersionInfo::instance().getMajor() == LL_VIEWER_VERSION_MAJOR); + CHECK(LLVersionInfo::instance().getMinor() == LL_VIEWER_VERSION_MINOR); + CHECK(LLVersionInfo::instance().getPatch() == LL_VIEWER_VERSION_PATCH); + CHECK(LLVersionInfo::instance().getBuild() == LL_VIEWER_VERSION_BUILD); + CHECK(LLVersionInfo::instance().getChannel() == ll_viewer_channel); + CHECK(LLVersionInfo::instance().getVersion() == mVersion); + CHECK(LLVersionInfo::instance().getShortVersion() == mShortVersion); + CHECK(LLVersionInfo::instance().getChannelAndVersion() == mVersionAndChannel); // The channel is the process's: put back however this ends. struct ChannelBack @@ -118,12 +97,8 @@ namespace tut ~ChannelBack() { LLVersionInfo::instance().resetChannel(was); } } channel_back; LLVersionInfo::instance().resetChannel(mResetChannel); - ensure_equals("Reset channel version", - LLVersionInfo::instance().getChannel(), - mResetChannel); + CHECK(LLVersionInfo::instance().getChannel() == mResetChannel); - ensure_equals("Reset Version and channel String", - LLVersionInfo::instance().getChannelAndVersion(), - mResetVersionAndChannel); + CHECK(LLVersionInfo::instance().getChannelAndVersion() == mResetVersionAndChannel); } } diff --git a/indra/newview/tests/llviewerassetstats_test.cpp b/indra/newview/tests/llviewerassetstats_test.cpp index 323affaded..2c1354b94a 100644 --- a/indra/newview/tests/llviewerassetstats_test.cpp +++ b/indra/newview/tests/llviewerassetstats_test.cpp @@ -27,10 +27,9 @@ #include "linden_common.h" -#include +#include #include -#include "lltut.h" #include "../llviewerassetstats.h" #include "lluuid.h" #include "llsdutil.h" @@ -237,10 +236,8 @@ get_region(const LLSD & sd, U64 region_handle1) return LLSD(); } -namespace tut +namespace { - namespace - { // The fixture's ThreadRecorder makes itself this thread's recorder, // with its own timer stack and current accumulators, and leaves all // of them null when it goes; the fixture makes it the master recorder @@ -270,7 +267,6 @@ namespace tut Current::setInstance(mTimers); } }; - } // anonymous namespace struct tst_viewerassetstats_index { @@ -288,16 +284,11 @@ namespace tut RecorderRestore mRestore; LLTrace::ThreadRecorder mThreadRecorder; }; - typedef test_group tst_viewerassetstats_index_t; - typedef tst_viewerassetstats_index_t::object tst_viewerassetstats_index_object_t; - tut::tst_viewerassetstats_index_t tut_tst_viewerassetstats_index("tst_viewerassetstats_test"); - // Testing free functions without global stats allocated - template<> template<> - void tst_viewerassetstats_index_object_t::test<1>() + TEST_CASE_METHOD(tst_viewerassetstats_index, "tst_viewerassetstats_test: the free functions run without global stats allocated", "[tst_viewerassetstats_test]") { // Check that helpers aren't bothered by missing global stats - ensure("Global gViewerAssetStats should be NULL", (NULL == gViewerAssetStats)); + CHECK(gViewerAssetStats == nullptr); LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, false, false); @@ -306,27 +297,27 @@ namespace tut LLViewerAssetStatsFF::record_response(LLViewerAssetType::AT_GESTURE, false, false, (U64Microseconds)12300000ULL); } - // Create a non-global instance and check the structure - template<> template<> - void tst_viewerassetstats_index_object_t::test<2>() + TEST_CASE_METHOD(tst_viewerassetstats_index, "tst_viewerassetstats_test: a non-global instance's LLSD has every key", "[tst_viewerassetstats_test]") { - ensure("Global gViewerAssetStats should be NULL", (NULL == gViewerAssetStats)); + CHECK(gViewerAssetStats == nullptr); LLViewerAssetStats * it = new LLViewerAssetStats(); - ensure("Global gViewerAssetStats should still be NULL", (NULL == gViewerAssetStats)); + UNSCOPED_INFO("making an instance does not make it the global"); + CHECK(gViewerAssetStats == nullptr); LLSD sd_full = it->asLLSD(false); // Default (NULL) region ID doesn't produce LLSD results so should // get an empty map back from output - ensure("Stat-less LLSD initially", is_no_stats_map(sd_full)); + UNSCOPED_INFO("Stat-less LLSD initially"); + CHECK(is_no_stats_map(sd_full)); // Once the region is set, we will get a response even with no data collection it->setRegion(region1_handle); sd_full = it->asLLSD(false); - ensure("Correct single-key LLSD map root", is_double_key_map(sd_full, "duration", "regions")); - ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd_full["regions"], region1_handle)); + CHECK(is_double_key_map(sd_full, "duration", "regions")); + CHECK(is_single_slot_array(sd_full["regions"], region1_handle)); LLSD sd = sd_full["regions"][0]; @@ -335,16 +326,16 @@ namespace tut // Check the structure of the LLSD for (int i = 0; i < LL_ARRAY_SIZE(all_keys); ++i) { - std::string line = llformat("Has '%s' key", all_keys[i]); - ensure(line, sd.has(all_keys[i])); + CAPTURE(all_keys[i]); + REQUIRE(sd.has(all_keys[i])); } for (int i = 0; i < LL_ARRAY_SIZE(resp_keys); ++i) { for (int j = 0; j < LL_ARRAY_SIZE(sub_keys); ++j) { - std::string line = llformat("Key '%s' has '%s' key", resp_keys[i], sub_keys[j]); - ensure(line, sd[resp_keys[i]].has(sub_keys[j])); + CAPTURE(resp_keys[i], sub_keys[j]); + REQUIRE(sd[resp_keys[i]].has(sub_keys[j])); } } @@ -352,34 +343,30 @@ namespace tut { for (int j = 0; j < LL_ARRAY_SIZE(mmm_sub_keys); ++j) { - std::string line = llformat("Key '%s' has '%s' key", mmm_resp_keys[i], mmm_sub_keys[j]); - ensure(line, sd[mmm_resp_keys[i]].has(mmm_sub_keys[j])); + CAPTURE(mmm_resp_keys[i], mmm_sub_keys[j]); + REQUIRE(sd[mmm_resp_keys[i]].has(mmm_sub_keys[j])); } } } - // Create a non-global instance and check some content - template<> template<> - void tst_viewerassetstats_index_object_t::test<3>() + TEST_CASE_METHOD(tst_viewerassetstats_index, "tst_viewerassetstats_test: a new non-global instance's counts start at zero", "[tst_viewerassetstats_test]") { LLViewerAssetStats * it = new LLViewerAssetStats(); it->setRegion(region1_handle); LLSD sd = it->asLLSD(false); - ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); - ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); + CHECK(is_double_key_map(sd, "regions", "duration")); + CHECK(is_single_slot_array(sd["regions"], region1_handle)); sd = sd[0]; delete it; // Check a few points on the tree for content - ensure("sd[get_texture_temp_http][dequeued] is 0", (0 == sd["get_texture_temp_http"]["dequeued"].asInteger())); - ensure("sd[get_sound_udp][resp_min] is 0", (0.0 == sd["get_sound_udp"]["resp_min"].asReal())); + CHECK(sd["get_texture_temp_http"]["dequeued"].asInteger() == 0); + CHECK(sd["get_sound_udp"]["resp_min"].asReal() == 0.0); } - // Create a global instance and verify free functions do something useful - template<> template<> - void tst_viewerassetstats_index_object_t::test<4>() + TEST_CASE_METHOD(tst_viewerassetstats_index, "tst_viewerassetstats_test: the free functions record into a global instance and reset clears it", "[tst_viewerassetstats_test]") { gViewerAssetStats = new LLViewerAssetStats(); LLViewerAssetStatsFF::set_region(region1_handle); @@ -391,16 +378,16 @@ namespace tut LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, false, false); LLSD sd = gViewerAssetStats->asLLSD(false); - ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); - ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); + CHECK(is_double_key_map(sd, "regions", "duration")); + CHECK(is_single_slot_array(sd["regions"], region1_handle)); sd = sd["regions"][0]; // Check a few points on the tree for content - ensure("sd[get_texture_non_temp_udp][enqueued] is 1", (1 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger())); - ensure("sd[get_texture_temp_udp][enqueued] is 0", (0 == sd["get_texture_temp_udp"]["enqueued"].asInteger())); - ensure("sd[get_texture_non_temp_http][enqueued] is 0", (0 == sd["get_texture_non_temp_http"]["enqueued"].asInteger())); - ensure("sd[get_texture_temp_http][enqueued] is 0", (0 == sd["get_texture_temp_http"]["enqueued"].asInteger())); - ensure("sd[get_gesture_udp][dequeued] is 0", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); + CHECK(sd["get_texture_non_temp_udp"]["enqueued"].asInteger() == 1); + CHECK(sd["get_texture_temp_udp"]["enqueued"].asInteger() == 0); + CHECK(sd["get_texture_non_temp_http"]["enqueued"].asInteger() == 0); + CHECK(sd["get_texture_temp_http"]["enqueued"].asInteger() == 0); + CHECK(sd["get_gesture_udp"]["dequeued"].asInteger() == 0); // Reset and check zeros... // Reset leaves current region in place @@ -410,13 +397,11 @@ namespace tut delete gViewerAssetStats; gViewerAssetStats = NULL; - ensure("sd[get_texture_non_temp_udp][enqueued] is reset", (0 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger())); - ensure("sd[get_gesture_udp][dequeued] is reset", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); + CHECK(sd["get_texture_non_temp_udp"]["enqueued"].asInteger() == 0); + CHECK(sd["get_gesture_udp"]["dequeued"].asInteger() == 0); } - // Check multiple region collection - template<> template<> - void tst_viewerassetstats_index_object_t::test<5>() + TEST_CASE_METHOD(tst_viewerassetstats_index, "tst_viewerassetstats_test: stats are collected per region across two regions", "[tst_viewerassetstats_test]") { gViewerAssetStats = new LLViewerAssetStats(); @@ -439,43 +424,43 @@ namespace tut // std::cout << sd << std::endl; - ensure("Correct double-key LLSD map root", is_double_key_map(sd, "duration", "regions")); - ensure("Correct double-slot LLSD array regions", is_double_slot_array(sd["regions"], region1_handle, region2_handle)); + CHECK(is_double_key_map(sd, "duration", "regions")); + CHECK(is_double_slot_array(sd["regions"], region1_handle, region2_handle)); LLSD sd1 = get_region(sd, region1_handle); LLSD sd2 = get_region(sd, region2_handle); - ensure("Region1 is present in results", sd1.isMap()); - ensure("Region2 is present in results", sd2.isMap()); + UNSCOPED_INFO("Region1 is present in results"); + REQUIRE(sd1.isMap()); + UNSCOPED_INFO("Region2 is present in results"); + REQUIRE(sd2.isMap()); // Check a few points on the tree for content - ensure_equals("sd1[get_texture_non_temp_udp][enqueued] is 1", sd1["get_texture_non_temp_udp"]["enqueued"].asInteger(), 1); - ensure_equals("sd1[get_texture_temp_udp][enqueued] is 0", sd1["get_texture_temp_udp"]["enqueued"].asInteger(), 0); - ensure_equals("sd1[get_texture_non_temp_http][enqueued] is 0", sd1["get_texture_non_temp_http"]["enqueued"].asInteger(), 0); - ensure_equals("sd1[get_texture_temp_http][enqueued] is 0", sd1["get_texture_temp_http"]["enqueued"].asInteger(), 0); - ensure_equals("sd1[get_gesture_udp][dequeued] is 0", sd1["get_gesture_udp"]["dequeued"].asInteger(), 0); + CHECK(sd1["get_texture_non_temp_udp"]["enqueued"].asInteger() == 1); + CHECK(sd1["get_texture_temp_udp"]["enqueued"].asInteger() == 0); + CHECK(sd1["get_texture_non_temp_http"]["enqueued"].asInteger() == 0); + CHECK(sd1["get_texture_temp_http"]["enqueued"].asInteger() == 0); + CHECK(sd1["get_gesture_udp"]["dequeued"].asInteger() == 0); // Check a few points on the tree for content - ensure("sd2[get_gesture_udp][enqueued] is 4", (4 == sd2["get_gesture_udp"]["enqueued"].asInteger())); - ensure("sd2[get_gesture_udp][dequeued] is 0", (0 == sd2["get_gesture_udp"]["dequeued"].asInteger())); - ensure("sd2[get_texture_non_temp_udp][enqueued] is 0", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger())); + CHECK(sd2["get_gesture_udp"]["enqueued"].asInteger() == 4); + CHECK(sd2["get_gesture_udp"]["dequeued"].asInteger() == 0); + CHECK(sd2["get_texture_non_temp_udp"]["enqueued"].asInteger() == 0); // Reset and check zeros... // Reset leaves current region in place gViewerAssetStats->reset(); sd = gViewerAssetStats->asLLSD(false); - ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); - ensure("Correct single-slot LLSD array regions (p2)", is_single_slot_array(sd["regions"], region2_handle)); + CHECK(is_double_key_map(sd, "regions", "duration")); + CHECK(is_single_slot_array(sd["regions"], region2_handle)); sd2 = sd["regions"][0]; delete gViewerAssetStats; gViewerAssetStats = NULL; - ensure("sd2[get_texture_non_temp_udp][enqueued] is reset", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger())); - ensure("sd2[get_gesture_udp][enqueued] is reset", (0 == sd2["get_gesture_udp"]["enqueued"].asInteger())); + CHECK(sd2["get_texture_non_temp_udp"]["enqueued"].asInteger() == 0); + CHECK(sd2["get_gesture_udp"]["enqueued"].asInteger() == 0); } - // Check multiple region collection jumping back-and-forth between regions - template<> template<> - void tst_viewerassetstats_index_object_t::test<6>() + TEST_CASE_METHOD(tst_viewerassetstats_index, "tst_viewerassetstats_test: stats per region add up across moves back and forth between regions", "[tst_viewerassetstats_test]") { gViewerAssetStats = new LLViewerAssetStats(); @@ -511,44 +496,45 @@ namespace tut LLSD sd = gViewerAssetStats->asLLSD(false); - ensure("Correct double-key LLSD map root", is_double_key_map(sd, "duration", "regions")); - ensure("Correct double-slot LLSD array regions", is_double_slot_array(sd["regions"], region1_handle, region2_handle)); + CHECK(is_double_key_map(sd, "duration", "regions")); + CHECK(is_double_slot_array(sd["regions"], region1_handle, region2_handle)); LLSD sd1 = get_region(sd, region1_handle); LLSD sd2 = get_region(sd, region2_handle); - ensure("Region1 is present in results", sd1.isMap()); - ensure("Region2 is present in results", sd2.isMap()); + UNSCOPED_INFO("Region1 is present in results"); + REQUIRE(sd1.isMap()); + UNSCOPED_INFO("Region2 is present in results"); + REQUIRE(sd2.isMap()); // Check a few points on the tree for content - ensure("sd1[get_texture_non_temp_udp][enqueued] is 1", (1 == sd1["get_texture_non_temp_udp"]["enqueued"].asInteger())); - ensure("sd1[get_texture_temp_udp][enqueued] is 0", (0 == sd1["get_texture_temp_udp"]["enqueued"].asInteger())); - ensure("sd1[get_texture_non_temp_http][enqueued] is 0", (0 == sd1["get_texture_non_temp_http"]["enqueued"].asInteger())); - ensure("sd1[get_texture_temp_http][enqueued] is 1", (1 == sd1["get_texture_temp_http"]["enqueued"].asInteger())); - ensure("sd1[get_gesture_udp][dequeued] is 0", (0 == sd1["get_gesture_udp"]["dequeued"].asInteger())); + CHECK(sd1["get_texture_non_temp_udp"]["enqueued"].asInteger() == 1); + CHECK(sd1["get_texture_temp_udp"]["enqueued"].asInteger() == 0); + CHECK(sd1["get_texture_non_temp_http"]["enqueued"].asInteger() == 0); + CHECK(sd1["get_texture_temp_http"]["enqueued"].asInteger() == 1); + CHECK(sd1["get_gesture_udp"]["dequeued"].asInteger() == 0); // Check a few points on the tree for content - ensure("sd2[get_gesture_udp][enqueued] is 8", (8 == sd2["get_gesture_udp"]["enqueued"].asInteger())); - ensure("sd2[get_gesture_udp][dequeued] is 0", (0 == sd2["get_gesture_udp"]["dequeued"].asInteger())); - ensure("sd2[get_texture_non_temp_udp][enqueued] is 0", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger())); + CHECK(sd2["get_gesture_udp"]["enqueued"].asInteger() == 8); + CHECK(sd2["get_gesture_udp"]["dequeued"].asInteger() == 0); + CHECK(sd2["get_texture_non_temp_udp"]["enqueued"].asInteger() == 0); // Reset and check zeros... // Reset leaves current region in place gViewerAssetStats->reset(); sd = gViewerAssetStats->asLLSD(false); - ensure("Correct single-key LLSD map root", is_double_key_map(sd, "duration", "regions")); - ensure("Correct single-slot LLSD array regions (p2)", is_single_slot_array(sd["regions"], region2_handle)); + CHECK(is_double_key_map(sd, "duration", "regions")); + CHECK(is_single_slot_array(sd["regions"], region2_handle)); sd2 = get_region(sd, region2_handle); - ensure("Region2 is present in results", sd2.isMap()); + UNSCOPED_INFO("Region2 is present in results"); + REQUIRE(sd2.isMap()); delete gViewerAssetStats; gViewerAssetStats = NULL; - ensure_equals("sd2[get_texture_non_temp_udp][enqueued] is reset", sd2["get_texture_non_temp_udp"]["enqueued"].asInteger(), 0); - ensure_equals("sd2[get_gesture_udp][enqueued] is reset", sd2["get_gesture_udp"]["enqueued"].asInteger(), 0); + CHECK(sd2["get_texture_non_temp_udp"]["enqueued"].asInteger() == 0); + CHECK(sd2["get_gesture_udp"]["enqueued"].asInteger() == 0); } - // Non-texture assets ignore transport and persistence flags - template<> template<> - void tst_viewerassetstats_index_object_t::test<7>() + TEST_CASE_METHOD(tst_viewerassetstats_index, "tst_viewerassetstats_test: non-texture assets ignore the transport and persistence flags", "[tst_viewerassetstats_test]") { gViewerAssetStats = new LLViewerAssetStats(); LLViewerAssetStatsFF::set_region(region1_handle); @@ -577,37 +563,39 @@ namespace tut LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_LSL_BYTECODE, true, true); LLSD sd = gViewerAssetStats->asLLSD(false); - ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); - ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); + CHECK(is_double_key_map(sd, "regions", "duration")); + CHECK(is_single_slot_array(sd["regions"], region1_handle)); sd = get_region(sd, region1_handle); - ensure("Region1 is present in results", sd.isMap()); + UNSCOPED_INFO("Region1 is present in results"); + REQUIRE(sd.isMap()); // Check a few points on the tree for content - ensure("sd[get_gesture_udp][enqueued] is 0", (0 == sd["get_gesture_udp"]["enqueued"].asInteger())); - ensure("sd[get_gesture_udp][dequeued] is 0", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); + CHECK(sd["get_gesture_udp"]["enqueued"].asInteger() == 0); + CHECK(sd["get_gesture_udp"]["dequeued"].asInteger() == 0); - ensure("sd[get_wearable_http][enqueued] is 2", (2 == sd["get_wearable_http"]["enqueued"].asInteger())); - ensure("sd[get_wearable_http][dequeued] is 2", (2 == sd["get_wearable_http"]["dequeued"].asInteger())); + CHECK(sd["get_wearable_http"]["enqueued"].asInteger() == 2); + CHECK(sd["get_wearable_http"]["dequeued"].asInteger() == 2); - ensure("sd[get_wearable_udp][enqueued] is 2", (2 == sd["get_wearable_udp"]["enqueued"].asInteger())); - ensure("sd[get_wearable_udp][dequeued] is 2", (2 == sd["get_wearable_udp"]["dequeued"].asInteger())); + CHECK(sd["get_wearable_udp"]["enqueued"].asInteger() == 2); + CHECK(sd["get_wearable_udp"]["dequeued"].asInteger() == 2); - ensure("sd[get_other_http][enqueued] is 2", (2 == sd["get_other_http"]["enqueued"].asInteger())); - ensure("sd[get_other_http][dequeued] is 0", (0 == sd["get_other_http"]["dequeued"].asInteger())); + CHECK(sd["get_other_http"]["enqueued"].asInteger() == 2); + CHECK(sd["get_other_http"]["dequeued"].asInteger() == 0); - ensure("sd[get_other_udp][enqueued] is 2", (2 == sd["get_other_udp"]["enqueued"].asInteger())); - ensure("sd[get_other_udp][dequeued] is 0", (0 == sd["get_other_udp"]["dequeued"].asInteger())); + CHECK(sd["get_other_udp"]["enqueued"].asInteger() == 2); + CHECK(sd["get_other_udp"]["dequeued"].asInteger() == 0); // Reset and check zeros... // Reset leaves current region in place gViewerAssetStats->reset(); sd = get_region(gViewerAssetStats->asLLSD(false), region1_handle); - ensure("Region1 is present in results", sd.isMap()); + UNSCOPED_INFO("Region1 is present in results"); + REQUIRE(sd.isMap()); delete gViewerAssetStats; gViewerAssetStats = NULL; - ensure_equals("sd[get_texture_non_temp_udp][enqueued] is reset", sd["get_texture_non_temp_udp"]["enqueued"].asInteger(), 0); - ensure_equals("sd[get_gesture_udp][dequeued] is reset", sd["get_gesture_udp"]["dequeued"].asInteger(), 0); + CHECK(sd["get_texture_non_temp_udp"]["enqueued"].asInteger() == 0); + CHECK(sd["get_gesture_udp"]["dequeued"].asInteger() == 0); } } diff --git a/indra/newview/tests/llviewercontrollistener_test.cpp b/indra/newview/tests/llviewercontrollistener_test.cpp index 8fb71b726d..43af275f4a 100644 --- a/indra/newview/tests/llviewercontrollistener_test.cpp +++ b/indra/newview/tests/llviewercontrollistener_test.cpp @@ -17,7 +17,9 @@ // std headers // external library headers // other Linden headers -#include "../test/lltut.h" +#include +#include +#include "altestmatchers.h" #include "../test/catch_and_store_what_in.h" // catch_what() #include "commoncontrol.h" #include "llcontrol.h" // LLControlGroup @@ -27,15 +29,12 @@ /***************************************************************************** * TUT *****************************************************************************/ -namespace tut -{ namespace { - void ensure_contains(const std::string& msg, const std::string& substr) + void require_contains(const std::string& msg, const std::string& substr) { - tut::ensure_contains("Exception does not contain " + substr, msg, substr); + REQUIRE_THAT(msg, Catch::Matchers::ContainsSubstring(substr)); } -} // anonymous namespace struct llviewercontrollistener_data { @@ -48,120 +47,96 @@ namespace Global.declareBOOL("boolvar", true, "bool variable"); } }; - typedef test_group llviewercontrollistener_group; - typedef llviewercontrollistener_group::object object; - llviewercontrollistener_group llviewercontrollistenergrp("llviewercontrollistener"); - template<> template<> - void object::test<1>() + TEST_CASE_METHOD(llviewercontrollistener_data, "llviewercontrollistener: CommonControl with no listener is left untested", "[llviewercontrollistener]") { - set_test_name("CommonControl no listener"); // Not implemented: the linker drags in LLViewerControlListener when // we bring in LLViewerControl. } - template<> template<> - void object::test<2>() + TEST_CASE_METHOD(llviewercontrollistener_data, "llviewercontrollistener: CommonControl::get of an unknown group names the group", "[llviewercontrollistener]") { - set_test_name("CommonControl bad group"); std::string threw{ catch_what( [](){ LL::CommonControl::get("Nonexistent", "Variable"); }) }; - ensure_contains(threw, "group"); - ensure_contains(threw, "Nonexistent"); + require_contains(threw, "group"); + require_contains(threw, "Nonexistent"); } - template<> template<> - void object::test<3>() + TEST_CASE_METHOD(llviewercontrollistener_data, "llviewercontrollistener: CommonControl::get of an unknown variable names the key", "[llviewercontrollistener]") { - set_test_name("CommonControl bad variable"); std::string threw{ catch_what( [](){ LL::CommonControl::get("FakeGlobal", "Nonexistent"); }) }; - ensure_contains(threw, "key"); - ensure_contains(threw, "Nonexistent"); + require_contains(threw, "key"); + require_contains(threw, "Nonexistent"); } - template<> template<> - void object::test<4>() + TEST_CASE_METHOD(llviewercontrollistener_data, "llviewercontrollistener: CommonControl::toggle of a string variable says it is non-boolean", "[llviewercontrollistener]") { - set_test_name("CommonControl toggle string"); std::string threw{ catch_what( [](){ LL::CommonControl::toggle("FakeGlobal", "strvar"); }) }; - ensure_contains(threw, "non-boolean"); - ensure_contains(threw, "strvar"); + require_contains(threw, "non-boolean"); + require_contains(threw, "strvar"); } - template<> template<> - void object::test<5>() + TEST_CASE_METHOD(llviewercontrollistener_data, "llviewercontrollistener: CommonControl::get_vars of an unknown group names the group", "[llviewercontrollistener]") { - set_test_name("CommonControl list bad group"); std::string threw{ catch_what( [](){ LL::CommonControl::get_vars("Nonexistent"); }) }; - ensure_contains(threw, "group"); - ensure_contains(threw, "Nonexistent"); + require_contains(threw, "group"); + require_contains(threw, "Nonexistent"); } - template<> template<> - void object::test<6>() + TEST_CASE_METHOD(llviewercontrollistener_data, "llviewercontrollistener: CommonControl::get returns each variable's value", "[llviewercontrollistener]") { - set_test_name("CommonControl get"); auto strvar{ LL::CommonControl::get("FakeGlobal", "strvar") }; - ensure_equals(strvar, "woof"); + CHECK(strvar == "woof"); auto boolvar{ LL::CommonControl::get("FakeGlobal", "boolvar") }; - ensure(boolvar); + REQUIRE(boolvar); } - template<> template<> - void object::test<7>() + TEST_CASE_METHOD(llviewercontrollistener_data, "llviewercontrollistener: CommonControl set and set_default and toggle return the new value", "[llviewercontrollistener]") { - set_test_name("CommonControl set, set_default, toggle"); - std::string newstr{ LL::CommonControl::set("FakeGlobal", "strvar", "mouse").asString() }; - ensure_equals(newstr, "mouse"); - ensure_equals(LL::CommonControl::get("FakeGlobal", "strvar").asString(), "mouse"); - ensure_equals(LL::CommonControl::set_default("FakeGlobal", "strvar").asString(), "woof"); + CHECK(newstr == "mouse"); + CHECK(LL::CommonControl::get("FakeGlobal", "strvar").asString() == "mouse"); + CHECK(LL::CommonControl::set_default("FakeGlobal", "strvar").asString() == "woof"); bool newbool{ LL::CommonControl::set("FakeGlobal", "boolvar", false) }; - ensure(! newbool); - ensure(! LL::CommonControl::get("FakeGlobal", "boolvar").asBoolean()); - ensure(LL::CommonControl::set_default("FakeGlobal", "boolvar").asBoolean()); - ensure(! LL::CommonControl::toggle("FakeGlobal", "boolvar").asBoolean()); + CHECK_FALSE(newbool); + CHECK_FALSE(LL::CommonControl::get("FakeGlobal", "boolvar").asBoolean()); + CHECK(LL::CommonControl::set_default("FakeGlobal", "boolvar").asBoolean()); + CHECK_FALSE(LL::CommonControl::toggle("FakeGlobal", "boolvar").asBoolean()); } - template<> template<> - void object::test<8>() + TEST_CASE_METHOD(llviewercontrollistener_data, "llviewercontrollistener: CommonControl::get_def describes a variable", "[llviewercontrollistener]") { - set_test_name("CommonControl get_def"); LLSD def{ LL::CommonControl::get_def("FakeGlobal", "strvar") }; - ensure_equals( - def, - llsd::map("name", "strvar", + CHECK_THAT(def, + ll_test::LLSDEquals(llsd::map("name", "strvar", "type", "String", "value", "woof", - "comment", "string variable")); + "comment", "string variable"))); } - template<> template<> - void object::test<9>() + TEST_CASE_METHOD(llviewercontrollistener_data, "llviewercontrollistener: CommonControl::get_groups lists each group once while it lives", "[llviewercontrollistener]") { - set_test_name("CommonControl get_groups"); // Every group the process has: the fixture's, and whatever else has // been made -- HeadlessUI's, another test's gSavedSettings. So the // fixture's is there once, and one more group made adds its name. const std::vector groups{ LL::CommonControl::get_groups() }; - ensure_equals("FakeGlobal", std::count(groups.begin(), groups.end(), "FakeGlobal"), std::ptrdiff_t(1)); + CHECK(std::count(groups.begin(), groups.end(), "FakeGlobal") == std::ptrdiff_t(1)); { LLControlGroup another{ "FakeAnother" }; const std::vector more{ LL::CommonControl::get_groups() }; - ensure_equals("one more", more.size(), groups.size() + 1); - ensure_equals("FakeAnother", std::count(more.begin(), more.end(), "FakeAnother"), std::ptrdiff_t(1)); + REQUIRE(more.size() == groups.size() + 1); + CHECK(std::count(more.begin(), more.end(), "FakeAnother") == std::ptrdiff_t(1)); } - ensure_equals("one fewer once it goes", LL::CommonControl::get_groups().size(), groups.size()); + UNSCOPED_INFO("one fewer once it goes"); + REQUIRE(LL::CommonControl::get_groups().size() == groups.size()); } - template<> template<> - void object::test<10>() + TEST_CASE_METHOD(llviewercontrollistener_data, "llviewercontrollistener: CommonControl::get_vars describes every variable of a group", "[llviewercontrollistener]") { - set_test_name("CommonControl get_vars"); LLSD vars{ LL::CommonControl::get_vars("FakeGlobal") }; // convert from array (unpredictable order) to map LLSD varsmap{ LLSD::emptyMap() }; @@ -170,9 +145,8 @@ namespace varsmap[var["name"].asString()] = var; } // comparing maps is order-insensitive - ensure_equals( - varsmap, - llsd::map( + CHECK_THAT(varsmap, + ll_test::LLSDEquals(llsd::map( "strvar", llsd::map("name", "strvar", "type", "String", @@ -182,6 +156,6 @@ namespace llsd::map("name", "boolvar", "type", "Boolean", "value", true, - "comment", "bool variable"))); + "comment", "bool variable")))); } -} // namespace tut +} // anonymous namespace diff --git a/indra/newview/tests/llviewerhelputil_test.cpp b/indra/newview/tests/llviewerhelputil_test.cpp index 9c9ae0e083..5ab4347a6e 100644 --- a/indra/newview/tests/llviewerhelputil_test.cpp +++ b/indra/newview/tests/llviewerhelputil_test.cpp @@ -27,7 +27,7 @@ // Precompiled header #include "../llviewerprecompiledheaders.h" -#include "../test/lltut.h" +#include #include "../llviewerhelputil.h" #include "../llweb.h" @@ -96,57 +96,56 @@ std::string LLWeb::expandURLSubstitutions(const std::string &url, //---------------------------------------------------------------------------- -namespace tut +namespace { - struct viewerhelputil - { - }; - - typedef test_group viewerhelputil_t; - typedef viewerhelputil_t::object viewerhelputil_object_t; - tut::viewerhelputil_t tut_viewerhelputil("LLViewerHelpUtil"); - - template<> template<> - void viewerhelputil_object_t::test<1>() + TEST_CASE("LLViewerHelpUtil: buildHelpURL substitutes the topic and the viewer tags into the format", "[LLViewerHelpUtil]") { std::string topic("test_topic"); std::string subresult; gHelpURL = "fooformat"; subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("no substitution tags", subresult, "fooformat"); + UNSCOPED_INFO("no substitution tags"); + CHECK(subresult == "fooformat"); gHelpURL = ""; subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("blank substitution format", subresult, ""); + UNSCOPED_INFO("blank substitution format"); + CHECK(subresult == ""); gHelpURL = "[TOPIC]"; subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("topic name", subresult, "test_topic"); + UNSCOPED_INFO("topic name"); + CHECK(subresult == "test_topic"); gHelpURL = "[LANGUAGE]"; gLanguage = ""; subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("simple substitution with blank", subresult, ""); + UNSCOPED_INFO("simple substitution with blank"); + CHECK(subresult == ""); gHelpURL = "[LANGUAGE]"; gLanguage = "Esperanto"; subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("simple substitution", subresult, "Esperanto"); + UNSCOPED_INFO("simple substitution"); + CHECK(subresult == "Esperanto"); gHelpURL = "http://secondlife.com/[LANGUAGE]"; gLanguage = "Gaelic"; subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("simple substitution with url", subresult, "http://secondlife.com/Gaelic"); + UNSCOPED_INFO("simple substitution with url"); + CHECK(subresult == "http://secondlife.com/Gaelic"); gHelpURL = "[XXX]"; subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("unknown substitution", subresult, "[XXX]"); + UNSCOPED_INFO("unknown substitution"); + CHECK(subresult == "[XXX]"); gHelpURL = "[LANGUAGE]/[LANGUAGE]"; gLanguage = "Esperanto"; subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("multiple substitution", subresult, "Esperanto/Esperanto"); + UNSCOPED_INFO("multiple substitution"); + CHECK(subresult == "Esperanto/Esperanto"); gHelpURL = "http://[CHANNEL]/[VERSION]/[LANGUAGE]/[OS]/[GRID]/[XXX]"; gChannel = "Second Life Test"; @@ -155,6 +154,7 @@ namespace tut gOS = "AmigaOS 2.1"; gGrid = "mysim"; subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("complex substitution", subresult, "http://Second Life Test/2.0/gaelic/AmigaOS 2.1/mysim/[XXX]"); + UNSCOPED_INFO("complex substitution"); + CHECK(subresult == "http://Second Life Test/2.0/gaelic/AmigaOS 2.1/mysim/[XXX]"); } } diff --git a/indra/newview/tests/llviewernetwork_test.cpp b/indra/newview/tests/llviewernetwork_test.cpp index 63501b09dd..db2a97725f 100644 --- a/indra/newview/tests/llviewernetwork_test.cpp +++ b/indra/newview/tests/llviewernetwork_test.cpp @@ -27,7 +27,7 @@ */ #include "../llviewerprecompiledheaders.h" #include "../llviewernetwork.h" -#include "../test/lltut.h" +#include #include "../../llxml/llcontrol.h" #include "llfile.h" @@ -168,7 +168,7 @@ static const char *gSampleGridFile = // ------------------------------------------------------------------------------------------- // TUT // ------------------------------------------------------------------------------------------- -namespace tut +namespace { // Test wrapper declaration : wrapping nothing for the moment struct viewerNetworkTest @@ -190,87 +190,64 @@ namespace tut } }; - // Tut templating thingamagic: test group, object and test instance - typedef test_group viewerNetworkTestFactory; - typedef viewerNetworkTestFactory::object viewerNetworkTestObject; - tut::viewerNetworkTestFactory tut_viewernetwork("LLViewerNetwork"); - // --------------------------------------------------------------------------------------- // Test functions // --------------------------------------------------------------------------------------- // initialization without a grid file - template<> template<> - void viewerNetworkTestObject::test<1>() + TEST_CASE_METHOD(viewerNetworkTest, "LLViewerNetwork: without a grid file Agni and Aditi are the known grids", "[LLViewerNetwork]") { LLGridManager *manager = LLGridManager::getInstance(); // grid file doesn't exist manager->initialize(TEST_FILENAME); // validate that some of the defaults are available. std::map known_grids = manager->getKnownGrids(); - ensure_equals("Known grids is a string-string map of size 2", known_grids.size(), 2); - ensure_equals("Agni has the right name and label", - known_grids[std::string("util.agni.lindenlab.com")], + REQUIRE(known_grids.size() == 2); + CHECK(known_grids[std::string("util.agni.lindenlab.com")] == std::string("Second Life Main Grid (Agni)")); - ensure_equals("Aditi has the right name and label", - known_grids[std::string("util.aditi.lindenlab.com")], + CHECK(known_grids[std::string("util.aditi.lindenlab.com")] == std::string("Second Life Beta Test Grid (Aditi)")); - ensure_equals("name for agni", - LLGridManager::getInstance()->getGrid("util.agni.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getGrid("util.agni.lindenlab.com") == std::string("util.agni.lindenlab.com")); - ensure_equals("id for agni", - std::string("Agni"), + CHECK(std::string("Agni") == LLGridManager::getInstance()->getGridId("util.agni.lindenlab.com")); - ensure_equals("update url base for Agni", // relies on agni being the default - std::string("https://update.secondlife.com/update"), + UNSCOPED_INFO("update url base for Agni"); + CHECK(std::string("https://update.secondlife.com/update") == LLGridManager::getInstance()->getUpdateServiceURL()); - ensure_equals("label for agni", - LLGridManager::getInstance()->getGridLabel("util.agni.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getGridLabel("util.agni.lindenlab.com") == std::string("Second Life Main Grid (Agni)")); std::vector login_uris; LLGridManager::getInstance()->getLoginURIs(std::string("util.agni.lindenlab.com"), login_uris); - ensure_equals("Number of login uris for agni", 1, login_uris.size()); - ensure_equals("Agni login uri", - login_uris[0], + REQUIRE(1 == login_uris.size()); + CHECK(login_uris[0] == std::string("https://login.agni.lindenlab.com/cgi-bin/login.cgi")); - ensure_equals("Agni helper uri", - LLGridManager::getInstance()->getHelperURI("util.agni.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getHelperURI("util.agni.lindenlab.com") == std::string("https://secondlife.com/helpers/")); - ensure_equals("Agni login page", - LLGridManager::getInstance()->getLoginPage("util.agni.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getLoginPage("util.agni.lindenlab.com") == std::string("https://viewer-splash-v2.secondlife.com/")); - ensure("Agni is a system grid", - LLGridManager::getInstance()->isSystemGrid("util.agni.lindenlab.com")); + CHECK(LLGridManager::getInstance()->isSystemGrid("util.agni.lindenlab.com")); - ensure_equals("name for aditi", - LLGridManager::getInstance()->getGrid("util.aditi.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getGrid("util.aditi.lindenlab.com") == std::string("util.aditi.lindenlab.com")); - ensure_equals("id for aditi", - LLGridManager::getInstance()->getGridId("util.aditi.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getGridId("util.aditi.lindenlab.com") == std::string("Aditi")); - ensure_equals("label for aditi", - LLGridManager::getInstance()->getGridLabel("util.aditi.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getGridLabel("util.aditi.lindenlab.com") == std::string("Second Life Beta Test Grid (Aditi)")); LLGridManager::getInstance()->getLoginURIs(std::string("util.aditi.lindenlab.com"), login_uris); - ensure_equals("Number of login uris for aditi", 1, login_uris.size()); - ensure_equals("Aditi login uri", - login_uris[0], + REQUIRE(1 == login_uris.size()); + CHECK(login_uris[0] == std::string("https://login.aditi.lindenlab.com/cgi-bin/login.cgi")); - ensure_equals("Aditi helper uri", - LLGridManager::getInstance()->getHelperURI("util.aditi.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getHelperURI("util.aditi.lindenlab.com") == std::string("https://secondlife.aditi.lindenlab.com/helpers/")); - ensure_equals("Aditi login page", - LLGridManager::getInstance()->getLoginPage("util.aditi.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getLoginPage("util.aditi.lindenlab.com") == std::string("https://viewer-splash-v2.secondlife.com/")); - ensure("Aditi is a system grid", - LLGridManager::getInstance()->isSystemGrid("util.aditi.lindenlab.com")); + CHECK(LLGridManager::getInstance()->isSystemGrid("util.aditi.lindenlab.com")); } // initialization with a grid file - template<> template<> - void viewerNetworkTestObject::test<2>() + TEST_CASE_METHOD(viewerNetworkTest, "LLViewerNetwork: a grid file adds its grids without overwriting Agni or Aditi", "[LLViewerNetwork]") { llofstream gridfile(TEST_FILENAME); gridfile << gSampleGridFile; @@ -278,129 +255,98 @@ namespace tut LLGridManager::getInstance()->initialize(TEST_FILENAME); std::map known_grids = LLGridManager::getInstance()->getKnownGrids(); - ensure_equals("adding a grid via a grid file increases known grid size",4, - known_grids.size()); + UNSCOPED_INFO("adding a grid via a grid file increases known grid size"); + REQUIRE(4 == known_grids.size()); // Verify that Agni and Aditi were not overwritten - ensure_equals("Agni has the right name and label", - known_grids[std::string("util.agni.lindenlab.com")], + CHECK(known_grids[std::string("util.agni.lindenlab.com")] == std::string("Second Life Main Grid (Agni)")); - ensure_equals("Aditi has the right name and label", - known_grids[std::string("util.aditi.lindenlab.com")], + CHECK(known_grids[std::string("util.aditi.lindenlab.com")] == std::string("Second Life Beta Test Grid (Aditi)")); - ensure_equals("name for agni", - LLGridManager::getInstance()->getGrid("util.agni.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getGrid("util.agni.lindenlab.com") == std::string("util.agni.lindenlab.com")); - ensure_equals("id for agni", - LLGridManager::getInstance()->getGridId("util.agni.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getGridId("util.agni.lindenlab.com") == std::string("Agni")); - ensure_equals("update url base for Agni", // relies on agni being the default - std::string("https://update.secondlife.com/update"), + UNSCOPED_INFO("update url base for Agni"); + CHECK(std::string("https://update.secondlife.com/update") == LLGridManager::getInstance()->getUpdateServiceURL()); - ensure_equals("label for agni", - LLGridManager::getInstance()->getGridLabel("util.agni.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getGridLabel("util.agni.lindenlab.com") == std::string("Second Life Main Grid (Agni)")); std::vector login_uris; LLGridManager::getInstance()->getLoginURIs(std::string("util.agni.lindenlab.com"), login_uris); - ensure_equals("Number of login uris for agni", 1, login_uris.size()); - ensure_equals("Agni login uri", - login_uris[0], + REQUIRE(1 == login_uris.size()); + CHECK(login_uris[0] == std::string("https://login.agni.lindenlab.com/cgi-bin/login.cgi")); - ensure_equals("Agni helper uri", - LLGridManager::getInstance()->getHelperURI("util.agni.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getHelperURI("util.agni.lindenlab.com") == std::string("https://secondlife.com/helpers/")); - ensure_equals("Agni login page", - LLGridManager::getInstance()->getLoginPage("util.agni.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getLoginPage("util.agni.lindenlab.com") == std::string("https://viewer-splash-v2.secondlife.com/")); - ensure("Agni is a system grid", - LLGridManager::getInstance()->isSystemGrid("util.agni.lindenlab.com")); + CHECK(LLGridManager::getInstance()->isSystemGrid("util.agni.lindenlab.com")); - ensure_equals("name for aditi", - LLGridManager::getInstance()->getGrid("util.aditi.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getGrid("util.aditi.lindenlab.com") == std::string("util.aditi.lindenlab.com")); - ensure_equals("id for aditi", - LLGridManager::getInstance()->getGridId("util.aditi.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getGridId("util.aditi.lindenlab.com") == std::string("Aditi")); - ensure_equals("label for aditi", - LLGridManager::getInstance()->getGridLabel("util.aditi.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getGridLabel("util.aditi.lindenlab.com") == std::string("Second Life Beta Test Grid (Aditi)")); LLGridManager::getInstance()->getLoginURIs(std::string("util.aditi.lindenlab.com"), login_uris); - ensure_equals("Number of login uris for aditi", 1, login_uris.size()); - ensure_equals("Aditi login uri", - login_uris[0], + REQUIRE(1 == login_uris.size()); + CHECK(login_uris[0] == std::string("https://login.aditi.lindenlab.com/cgi-bin/login.cgi")); - ensure_equals("Aditi helper uri", - LLGridManager::getInstance()->getHelperURI("util.aditi.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getHelperURI("util.aditi.lindenlab.com") == std::string("https://secondlife.aditi.lindenlab.com/helpers/")); - ensure_equals("Aditi login page", - LLGridManager::getInstance()->getLoginPage("util.aditi.lindenlab.com"), + CHECK(LLGridManager::getInstance()->getLoginPage("util.aditi.lindenlab.com") == std::string("https://viewer-splash-v2.secondlife.com/")); - ensure("Aditi is a system grid", - LLGridManager::getInstance()->isSystemGrid("util.aditi.lindenlab.com")); + CHECK(LLGridManager::getInstance()->isSystemGrid("util.aditi.lindenlab.com")); // Check the additional grid from the file - ensure_equals("alternative grid is in name<->label map", - known_grids["altgrid.long.name"], + UNSCOPED_INFO("alternative grid is in name<->label map"); + CHECK(known_grids["altgrid.long.name"] == std::string("Alternative Grid")); - ensure_equals("alternative grid name is set", - LLGridManager::getInstance()->getGrid("altgrid.long.name"), + CHECK(LLGridManager::getInstance()->getGrid("altgrid.long.name") == std::string("altgrid.long.name")); - ensure_equals("alternative grid id", - LLGridManager::getInstance()->getGridId("altgrid.long.name"), + CHECK(LLGridManager::getInstance()->getGridId("altgrid.long.name") == std::string("AltGrid")); - ensure_equals("alternative grid label", - LLGridManager::getInstance()->getGridLabel("altgrid.long.name"), + CHECK(LLGridManager::getInstance()->getGridLabel("altgrid.long.name") == std::string("Alternative Grid")); std::vector alt_login_uris; LLGridManager::getInstance()->getLoginURIs(std::string("altgrid.long.name"), alt_login_uris); - ensure_equals("Number of login uris for altgrid", 2, alt_login_uris.size()); - ensure_equals("alternative grid first login uri", - alt_login_uris[0], + REQUIRE(2 == alt_login_uris.size()); + CHECK(alt_login_uris[0] == std::string("altgrid/myloginuri1")); - ensure_equals("alternative grid second login uri", - alt_login_uris[1], + CHECK(alt_login_uris[1] == std::string("altgrid/myloginuri2")); - ensure_equals("alternative grid helper uri", - LLGridManager::getInstance()->getHelperURI("altgrid.long.name"), + CHECK(LLGridManager::getInstance()->getHelperURI("altgrid.long.name") == std::string("https://helper1/helpers/")); - ensure_equals("alternative grid login page", - LLGridManager::getInstance()->getLoginPage("altgrid.long.name"), + CHECK(LLGridManager::getInstance()->getLoginPage("altgrid.long.name") == std::string("altgrid/loginpage")); - ensure("alternative grid is NOT a system grid", - ! LLGridManager::getInstance()->isSystemGrid("altgrid.long.name")); + CHECK_FALSE(LLGridManager::getInstance()->isSystemGrid("altgrid.long.name")); - ensure_equals("minimal grid is in name<->label map", - known_grids["minimal.long.name"], + UNSCOPED_INFO("minimal grid is in name<->label map"); + CHECK(known_grids["minimal.long.name"] == std::string("minimal.long.name")); - ensure_equals("minimal grid name is set", - LLGridManager::getInstance()->getGrid("minimal.long.name"), + CHECK(LLGridManager::getInstance()->getGrid("minimal.long.name") == std::string("minimal.long.name")); - ensure_equals("minimal grid id", - LLGridManager::getInstance()->getGridId("minimal.long.name"), + CHECK(LLGridManager::getInstance()->getGridId("minimal.long.name") == std::string("minimal.long.name")); - ensure_equals("minimal grid label", - LLGridManager::getInstance()->getGridLabel("minimal.long.name"), + CHECK(LLGridManager::getInstance()->getGridLabel("minimal.long.name") == std::string("minimal.long.name")); LLGridManager::getInstance()->getLoginURIs(std::string("minimal.long.name"), alt_login_uris); - ensure_equals("Number of login uris for altgrid", 1, alt_login_uris.size()); - ensure_equals("minimal grid login uri", - alt_login_uris[0], + REQUIRE(1 == alt_login_uris.size()); + CHECK(alt_login_uris[0] == std::string("https://minimal.long.name/cgi-bin/login.cgi")); - ensure_equals("minimal grid helper uri", - LLGridManager::getInstance()->getHelperURI("minimal.long.name"), + CHECK(LLGridManager::getInstance()->getHelperURI("minimal.long.name") == std::string("https://minimal.long.name/helpers/")); - ensure_equals("minimal grid login page", - LLGridManager::getInstance()->getLoginPage("minimal.long.name"), + CHECK(LLGridManager::getInstance()->getLoginPage("minimal.long.name") == std::string("https://minimal.long.name/app/login/")); } // validate grid selection - template<> template<> - void viewerNetworkTestObject::test<7>() + TEST_CASE_METHOD(viewerNetworkTest, "LLViewerNetwork: setGridChoice selects the grid the getters answer for", "[LLViewerNetwork]") { // adding a grid with simply a name will populate the values. llofstream gridfile(TEST_FILENAME); @@ -410,43 +356,36 @@ namespace tut LLGridManager::getInstance()->initialize(TEST_FILENAME); LLGridManager::getInstance()->setGridChoice("util.agni.lindenlab.com"); - ensure_equals("getGridLabel", - LLGridManager::getInstance()->getGridLabel(), + CHECK(LLGridManager::getInstance()->getGridLabel() == std::string("Second Life Main Grid (Agni)")); - ensure_equals("getGridId", - LLGridManager::getInstance()->getGridId(), + CHECK(LLGridManager::getInstance()->getGridId() == std::string("Agni")); - ensure_equals("getGrid", - LLGridManager::getInstance()->getGrid(), + CHECK(LLGridManager::getInstance()->getGrid() == std::string("util.agni.lindenlab.com")); - ensure_equals("getHelperURI", - LLGridManager::getInstance()->getHelperURI(), + CHECK(LLGridManager::getInstance()->getHelperURI() == std::string("https://secondlife.com/helpers/")); - ensure_equals("getLoginPage", - LLGridManager::getInstance()->getLoginPage(), + CHECK(LLGridManager::getInstance()->getLoginPage() == std::string("https://viewer-splash-v2.secondlife.com/")); - ensure_equals("update url base for Agni", // relies on agni being the default - std::string("https://update.secondlife.com/update"), + UNSCOPED_INFO("update url base for Agni"); + CHECK(std::string("https://update.secondlife.com/update") == LLGridManager::getInstance()->getUpdateServiceURL()); - ensure("Is Agni a production grid", LLGridManager::getInstance()->isInProductionGrid()); + UNSCOPED_INFO("Agni is a production grid"); + CHECK(LLGridManager::getInstance()->isInProductionGrid()); std::vector uris; LLGridManager::getInstance()->getLoginURIs(uris); - ensure_equals("getLoginURIs size", 1, uris.size()); - ensure_equals("getLoginURIs", - uris[0], + REQUIRE(1 == uris.size()); + CHECK(uris[0] == std::string("https://login.agni.lindenlab.com/cgi-bin/login.cgi")); LLGridManager::getInstance()->setGridChoice("altgrid.long.name"); - ensure_equals("getGridLabel", - LLGridManager::getInstance()->getGridLabel(), + CHECK(LLGridManager::getInstance()->getGridLabel() == std::string("Alternative Grid")); - ensure_equals("getGridId", - LLGridManager::getInstance()->getGridId(), + CHECK(LLGridManager::getInstance()->getGridId() == std::string("AltGrid")); - ensure("alternative grid is not a system grid", - !LLGridManager::getInstance()->isSystemGrid()); - ensure("alternative grid is not a production grid", - !LLGridManager::getInstance()->isInProductionGrid()); + UNSCOPED_INFO("alternative grid is not a system grid"); + CHECK_FALSE(LLGridManager::getInstance()->isSystemGrid()); + UNSCOPED_INFO("alternative grid is not a production grid"); + CHECK_FALSE(LLGridManager::getInstance()->isInProductionGrid()); } } diff --git a/indra/newview/tests/llvocache_test.cpp b/indra/newview/tests/llvocache_test.cpp index d4076625e0..d1c4fb8856 100644 --- a/indra/newview/tests/llvocache_test.cpp +++ b/indra/newview/tests/llvocache_test.cpp @@ -26,7 +26,7 @@ * $/LicenseInfo$ */ #include "../llviewerprecompiledheaders.h" -#include "../test/lltut.h" +#include #include "../llvocache.h" @@ -80,7 +80,7 @@ LLViewerRegion* LLWorld::getRegionFromHandle(const U64&) { return nullptr; } // ------------------------------------------------------------------------------------------- // TUT // ------------------------------------------------------------------------------------------- -namespace tut +namespace { // Each test gets a fresh cache singleton with the real LLDir's cache // directory pointed at a temporary directory of its own, so tests neither @@ -96,7 +96,8 @@ namespace tut { mRoot = std::filesystem::temp_directory_path() / ("llvocache_test_" + LLUUID::generateNewID().asString()); std::filesystem::create_directories(mRoot); - ensure("cache dir redirected", gDirUtilp->setCacheDir(mRoot.string())); + UNSCOPED_INFO("cache dir redirected"); + REQUIRE(gDirUtilp->setCacheDir(mRoot.string())); LLVOCache::initParamSingleton(/*read_only=*/false); LLVOCache::instance().initCache(LL_PATH_CACHE, CACHE_NUMBER_OF_REGIONS, CACHE_VERSION); @@ -144,15 +145,20 @@ namespace tut return entries; } - static void ensureSameEntry(const LLVOCacheEntry& expected, const LLVOCacheEntry& actual) + static void requireSameEntry(const LLVOCacheEntry& expected, const LLVOCacheEntry& actual) { - ensure_equals("crc", actual.getCRC(), expected.getCRC()); - ensure_equals("hit count", actual.getHitCount(), expected.getHitCount()); + REQUIRE(actual.getCRC() == expected.getCRC()); + REQUIRE(actual.getHitCount() == expected.getHitCount()); const LLDataPackerBinaryBuffer* a = expected.getDP(); const LLDataPackerBinaryBuffer* b = actual.getDP(); - ensure("body present", a && b); - ensure_equals("body size", b->getBufferSize(), a->getBufferSize()); - ensure("body bytes", memcmp(a->getBuffer(), b->getBuffer(), (size_t)a->getBufferSize()) == 0); + { + INFO("body present"); + REQUIRE(a); + REQUIRE(b); + } + REQUIRE(b->getBufferSize() == a->getBufferSize()); + UNSCOPED_INFO("body bytes"); + REQUIRE(memcmp(a->getBuffer(), b->getBuffer(), (size_t)a->getBufferSize()) == 0); } // An override message in the shape LLGLTFOverrideCacheEntry::fromLLSD @@ -181,10 +187,11 @@ namespace tut return data; } - static LLGLTFOverrideCacheEntry makeOverride(U32 local_id, U64 handle, int sides) + static LLGLTFOverrideCacheEntry requireOverride(U32 local_id, U64 handle, int sides) { LLGLTFOverrideCacheEntry entry; - ensure("override parses", entry.fromLLSD(makeOverrideLLSD(local_id, handle, LLUUID::generateNewID(), sides))); + UNSCOPED_INFO("override parses"); + REQUIRE(entry.fromLLSD(makeOverrideLLSD(local_id, handle, LLUUID::generateNewID(), sides))); return entry; } @@ -255,117 +262,122 @@ namespace tut LLVOCache::instance().initCache(LL_PATH_CACHE, CACHE_NUMBER_OF_REGIONS, CACHE_VERSION); } - static void ensureSameOverride(const LLGLTFOverrideCacheEntry& expected, const LLGLTFOverrideCacheEntry& actual) + static void requireSameOverride(const LLGLTFOverrideCacheEntry& expected, const LLGLTFOverrideCacheEntry& actual) { - ensure_equals("object id", actual.mObjectId, expected.mObjectId); - ensure_equals("local id", actual.mLocalId, expected.mLocalId); - ensure_equals("region handle", actual.mRegionHandle, expected.mRegionHandle); - ensure_equals("side count", actual.mSides.size(), expected.mSides.size()); + REQUIRE(actual.mObjectId == expected.mObjectId); + REQUIRE(actual.mLocalId == expected.mLocalId); + REQUIRE(actual.mRegionHandle == expected.mRegionHandle); + REQUIRE(actual.mSides.size() == expected.mSides.size()); for (const auto& [side, llsd] : expected.mSides) { + CAPTURE(side); auto found = actual.mSides.find(side); - ensure("side present", found != actual.mSides.end()); - ensure("side llsd", llsd_equals(found->second, llsd)); + UNSCOPED_INFO("side present"); + REQUIRE(found != actual.mSides.end()); + REQUIRE(llsd_equals(found->second, llsd)); } } }; - // The harness's main() owns APR for the run; a group factory is a static - // and must not tear it down a second time on the way out. - struct vocacheTestFactory : public test_group - { - vocacheTestFactory() : test_group("LLVOCache") {} - }; - - typedef vocacheTestFactory::object vocacheTestObject; - tut::vocacheTestFactory tut_vocache; - // --------------------------------------------------------------------------------------- // Test functions // --------------------------------------------------------------------------------------- - template<> template<> - void vocacheTestObject::test<1>() + TEST_CASE_METHOD(vocacheTest, "LLVOCache: an override entry round-trips LLSD and builds materials on demand", "[LLVOCache]") { - set_test_name("override entry round trip, materials built on demand"); - const U64 handle = to_region_handle(1000 * 256, 1000 * 256); const LLUUID object_id = LLUUID::generateNewID(); LLSD data = makeOverrideLLSD(42, handle, object_id, 3); LLGLTFOverrideCacheEntry entry; - ensure("fromLLSD", entry.fromLLSD(data)); - ensure_equals("object id", entry.mObjectId, object_id); - ensure_equals("local id", entry.mLocalId, 42u); - ensure_equals("region handle", entry.mRegionHandle, handle); - ensure_equals("sides", entry.mSides.size(), (size_t)3); - ensure("materials not built by loading", entry.mGLTFMaterial.empty()); + REQUIRE(entry.fromLLSD(data)); + CHECK(entry.mObjectId == object_id); + CHECK(entry.mLocalId == 42u); + CHECK(entry.mRegionHandle == handle); + REQUIRE(entry.mSides.size() == (size_t)3); + UNSCOPED_INFO("materials not built by loading"); + CHECK(entry.mGLTFMaterial.empty()); // toLLSD walks a hash map, so compare per side rather than as arrays. LLSD back = entry.toLLSD(); - ensure_equals("local id survives", back["local_id"].asInteger(), 42); - ensure_equals("object id survives", back["object_id"].asUUID(), object_id); - ensure_equals("region x survives", back["region_handle_x"].asInteger(), data["region_handle_x"].asInteger()); - ensure_equals("region y survives", back["region_handle_y"].asInteger(), data["region_handle_y"].asInteger()); - ensure_equals("side count survives", back["sides"].size(), (size_t)3); + UNSCOPED_INFO("local id survives"); + CHECK(back["local_id"].asInteger() == 42); + UNSCOPED_INFO("object id survives"); + CHECK(back["object_id"].asUUID() == object_id); + UNSCOPED_INFO("region x survives"); + CHECK(back["region_handle_x"].asInteger() == data["region_handle_x"].asInteger()); + UNSCOPED_INFO("region y survives"); + CHECK(back["region_handle_y"].asInteger() == data["region_handle_y"].asInteger()); + UNSCOPED_INFO("side count survives"); + REQUIRE(back["sides"].size() == (size_t)3); for (size_t i = 0; i < back["sides"].size(); ++i) { S32 side = back["sides"][i].asInteger(); - ensure("side llsd survives", llsd_equals(back["gltf_llsd"][i], data["gltf_llsd"][side])); + CAPTURE(side); + UNSCOPED_INFO("side llsd survives"); + REQUIRE(llsd_equals(back["gltf_llsd"][i], data["gltf_llsd"][side])); } entry.materialize(); - ensure_equals("one material per side", entry.mGLTFMaterial.size(), entry.mSides.size()); + UNSCOPED_INFO("one material per side"); + REQUIRE(entry.mGLTFMaterial.size() == entry.mSides.size()); for (const auto& [side, llsd] : entry.mSides) { - ensure("material for side", entry.mGLTFMaterial.find(side) != entry.mGLTFMaterial.end()); - ensure("material built", entry.mGLTFMaterial.at(side).notNull()); + CAPTURE(side); + UNSCOPED_INFO("material for side"); + REQUIRE(entry.mGLTFMaterial.find(side) != entry.mGLTFMaterial.end()); + UNSCOPED_INFO("material built"); + REQUIRE(entry.mGLTFMaterial.at(side).notNull()); } LLGLTFMaterial* first = entry.mGLTFMaterial.at(0).get(); entry.materialize(); - ensure("second materialize keeps the materials", entry.mGLTFMaterial.at(0).get() == first); + UNSCOPED_INFO("second materialize keeps the materials"); + CHECK(entry.mGLTFMaterial.at(0).get() == first); LLGLTFOverrideCacheEntry rejected; LLSD headless = data; headless.erase("region_handle_x"); - ensure("no region handle is rejected", !rejected.fromLLSD(headless)); + UNSCOPED_INFO("no region handle is rejected"); + CHECK_FALSE(rejected.fromLLSD(headless)); headless = data; headless.erase("local_id"); - ensure("no local id is rejected", !rejected.fromLLSD(headless)); + UNSCOPED_INFO("no local id is rejected"); + CHECK_FALSE(rejected.fromLLSD(headless)); } - template<> template<> - void vocacheTestObject::test<2>() + TEST_CASE_METHOD(vocacheTest, "LLVOCache: an object cache file round-trips every entry", "[LLVOCache]") { - set_test_name("object cache round trip"); - const U64 handle = to_region_handle(1000 * 256, 1000 * 256); const LLUUID id = LLUUID::generateNewID(); LLVOCacheEntry::vocache_entry_map_t written = makeEntries(500); LLVOCache::instance().writeToCache(handle, id, written, /*dirty=*/true, /*removal_enabled=*/false); - ensure("cache file written", std::filesystem::exists(objectFile(handle, ".slc"))); + UNSCOPED_INFO("cache file written"); + CHECK(std::filesystem::exists(objectFile(handle, ".slc"))); LLVOCacheEntry::vocache_entry_map_t read; - ensure("read succeeds", LLVOCache::instance().readFromCache(handle, id, read)); - ensure_equals("entry count", read.size(), written.size()); + UNSCOPED_INFO("read succeeds"); + REQUIRE(LLVOCache::instance().readFromCache(handle, id, read)); + UNSCOPED_INFO("entry count"); + REQUIRE(read.size() == written.size()); for (const auto& [local_id, entry] : written) { + CAPTURE(local_id); auto found = read.find(local_id); - ensure("entry present", found != read.end()); - ensureSameEntry(*entry, *found->second); + UNSCOPED_INFO("entry present"); + REQUIRE(found != read.end()); + requireSameEntry(*entry, *found->second); } LLVOCacheEntry::vocache_entry_map_t other; - ensure("another region's id is refused", !LLVOCache::instance().readFromCache(handle, LLUUID::generateNewID(), other)); - ensure("nothing loaded for it", other.empty()); + UNSCOPED_INFO("another region's id is refused"); + CHECK_FALSE(LLVOCache::instance().readFromCache(handle, LLUUID::generateNewID(), other)); + UNSCOPED_INFO("nothing loaded for it"); + CHECK(other.empty()); } - template<> template<> - void vocacheTestObject::test<3>() + TEST_CASE_METHOD(vocacheTest, "LLVOCache: a save with removal enabled counts only what it wrote", "[LLVOCache]") { - set_test_name("a save with removal enabled counts only what it wrote"); - const U64 handle = to_region_handle(1001 * 256, 1000 * 256); const LLUUID id = LLUUID::generateNewID(); LLVOCacheEntry::vocache_entry_map_t written = makeEntries(200); @@ -375,24 +387,25 @@ namespace tut entry->setValid(local_id % 3 != 0); valid += entry->isValid() ? 1 : 0; } - ensure("some entries were invalidated", valid < written.size()); + UNSCOPED_INFO("some entries were invalidated"); + REQUIRE(valid < written.size()); LLVOCache::instance().writeToCache(handle, id, written, true, /*removal_enabled=*/true); LLVOCacheEntry::vocache_entry_map_t read; - ensure("read succeeds against the written count", LLVOCache::instance().readFromCache(handle, id, read)); - ensure_equals("only valid entries were written", read.size(), valid); + UNSCOPED_INFO("read succeeds against the written count"); + REQUIRE(LLVOCache::instance().readFromCache(handle, id, read)); + UNSCOPED_INFO("only valid entries were written"); + REQUIRE(read.size() == valid); for (const auto& [local_id, entry] : read) { - ensure("no invalid entry came back", written.at(local_id)->isValid()); + UNSCOPED_INFO("no invalid entry came back"); + REQUIRE(written.at(local_id)->isValid()); } } - template<> template<> - void vocacheTestObject::test<4>() + TEST_CASE_METHOD(vocacheTest, "LLVOCache: a truncated object cache is refused", "[LLVOCache]") { - set_test_name("a truncated object cache is refused"); - const U64 handle = to_region_handle(1002 * 256, 1000 * 256); const LLUUID id = LLUUID::generateNewID(); LLVOCacheEntry::vocache_entry_map_t written = makeEntries(100); @@ -403,15 +416,14 @@ namespace tut std::filesystem::resize_file(file, size - 100); LLVOCacheEntry::vocache_entry_map_t read; - ensure("truncated file fails the read", !LLVOCache::instance().readFromCache(handle, id, read)); - ensure("fewer entries than written", read.size() < written.size()); + UNSCOPED_INFO("truncated file fails the read"); + CHECK_FALSE(LLVOCache::instance().readFromCache(handle, id, read)); + UNSCOPED_INFO("fewer entries than written"); + CHECK(read.size() < written.size()); } - template<> template<> - void vocacheTestObject::test<5>() + TEST_CASE_METHOD(vocacheTest, "LLVOCache: the header survives a restart", "[LLVOCache]") { - set_test_name("the header survives a restart"); - const U64 handle = to_region_handle(1003 * 256, 1000 * 256); const LLUUID id = LLUUID::generateNewID(); LLVOCacheEntry::vocache_entry_map_t written = makeEntries(50); @@ -422,15 +434,14 @@ namespace tut LLVOCache::instance().initCache(LL_PATH_CACHE, CACHE_NUMBER_OF_REGIONS, CACHE_VERSION); LLVOCacheEntry::vocache_entry_map_t read; - ensure("region known after restart", LLVOCache::instance().readFromCache(handle, id, read)); - ensure_equals("entries after restart", read.size(), written.size()); + UNSCOPED_INFO("region known after restart"); + REQUIRE(LLVOCache::instance().readFromCache(handle, id, read)); + UNSCOPED_INFO("entries after restart"); + CHECK(read.size() == written.size()); } - template<> template<> - void vocacheTestObject::test<6>() + TEST_CASE_METHOD(vocacheTest, "LLVOCache: the override cache round-trips and drops overrides with no object", "[LLVOCache]") { - set_test_name("override cache round trip"); - const U64 handle = to_region_handle(1004 * 256, 1000 * 256); const LLUUID id = LLUUID::generateNewID(); LLVOCacheEntry::vocache_entry_map_t objects = makeEntries(20); @@ -439,35 +450,40 @@ namespace tut LLVOCacheEntry::vocache_gltf_overrides_map_t written; for (U32 local_id = 1; local_id <= 20; local_id += 3) { - written[local_id] = makeOverride(local_id, handle, 1 + (int)(local_id % 4)); + written[local_id] = requireOverride(local_id, handle, 1 + (int)(local_id % 4)); } // An override for an object the primary cache does not hold is dropped on load. - written[999] = makeOverride(999, handle, 2); + written[999] = requireOverride(999, handle, 2); LLVOCache::instance().writeGenericExtrasToCache(handle, id, written, true, false); - ensure("extras file written", std::filesystem::exists(objectFile(handle, "_extras.slec"))); + UNSCOPED_INFO("extras file written"); + CHECK(std::filesystem::exists(objectFile(handle, "_extras.slec"))); LLVOCacheEntry::vocache_gltf_overrides_map_t read; - ensure("read succeeds", LLVOCache::instance().readGenericExtrasFromCache(handle, id, read, objects)); - ensure_equals("all overrides with an object came back", read.size(), written.size() - 1); - ensure("orphan override dropped", read.find(999) == read.end()); + UNSCOPED_INFO("read succeeds"); + REQUIRE(LLVOCache::instance().readGenericExtrasFromCache(handle, id, read, objects)); + UNSCOPED_INFO("all overrides with an object came back"); + CHECK(read.size() == written.size() - 1); + UNSCOPED_INFO("orphan override dropped"); + CHECK(read.find(999) == read.end()); for (const auto& [local_id, entry] : read) { - ensureSameOverride(written.at(local_id), entry); - ensure("materials not built by loading", entry.mGLTFMaterial.empty()); + CAPTURE(local_id); + requireSameOverride(written.at(local_id), entry); + UNSCOPED_INFO("materials not built by loading"); + REQUIRE(entry.mGLTFMaterial.empty()); } LLVOCacheEntry::vocache_gltf_overrides_map_t none; LLVOCacheEntry::vocache_entry_map_t no_objects; - ensure("empty primary cache is not a failure", LLVOCache::instance().readGenericExtrasFromCache(handle, id, none, no_objects)); - ensure("and loads nothing", none.empty()); + UNSCOPED_INFO("empty primary cache is not a failure"); + CHECK(LLVOCache::instance().readGenericExtrasFromCache(handle, id, none, no_objects)); + UNSCOPED_INFO("and loads nothing"); + CHECK(none.empty()); } - template<> template<> - void vocacheTestObject::test<7>() + TEST_CASE_METHOD(vocacheTest, "LLVOCache: a corrupt override record is skipped and a truncated file refused", "[LLVOCache]") { - set_test_name("a corrupt override record is skipped, a truncated file refused"); - const U64 handle = to_region_handle(1005 * 256, 1000 * 256); const LLUUID id = LLUUID::generateNewID(); LLVOCacheEntry::vocache_entry_map_t objects = makeEntries(10); @@ -476,7 +492,7 @@ namespace tut LLVOCacheEntry::vocache_gltf_overrides_map_t written; for (U32 local_id = 1; local_id <= 10; ++local_id) { - written[local_id] = makeOverride(local_id, handle, 2); + written[local_id] = requireOverride(local_id, handle, 2); } LLVOCache::instance().writeGenericExtrasToCache(handle, id, written, true, false); @@ -488,20 +504,24 @@ namespace tut } // version line, cache id, count, then records of [length][llsd] const size_t line_end = bytes.find('\n'); - ensure("version line present", line_end != std::string::npos); - ensure_equals("version line", bytes.substr(0, line_end), LLGLTFOverrideCacheEntry::VERSION_LABEL + ":" + std::to_string(LLGLTFOverrideCacheEntry::VERSION)); + UNSCOPED_INFO("version line present"); + REQUIRE(line_end != std::string::npos); + UNSCOPED_INFO("version line"); + CHECK(bytes.substr(0, line_end) == LLGLTFOverrideCacheEntry::VERSION_LABEL + ":" + std::to_string(LLGLTFOverrideCacheEntry::VERSION)); size_t cursor = line_end + 1 + UUID_BYTES; U32 count = 0; memcpy(&count, bytes.data() + cursor, sizeof(U32)); cursor += sizeof(U32); - ensure_equals("record count", (size_t)count, written.size()); + UNSCOPED_INFO("record count"); + REQUIRE((size_t)count == written.size()); // Overwrite the second record's first byte with a type tag LLSD binary // does not have, so that one record and only that one fails to parse. U32 first_length = 0; memcpy(&first_length, bytes.data() + cursor, sizeof(U32)); const size_t second_record = cursor + sizeof(U32) + first_length + sizeof(U32); - ensure("second record inside file", second_record < bytes.size()); + UNSCOPED_INFO("second record inside file"); + REQUIRE(second_record < bytes.size()); std::string corrupted = bytes; corrupted[second_record] = (char)0xFF; { @@ -510,11 +530,14 @@ namespace tut } LLVOCacheEntry::vocache_gltf_overrides_map_t read; - ensure("corrupt record does not fail the file", LLVOCache::instance().readGenericExtrasFromCache(handle, id, read, objects)); - ensure_equals("one record skipped", read.size(), written.size() - 1); + UNSCOPED_INFO("corrupt record does not fail the file"); + REQUIRE(LLVOCache::instance().readGenericExtrasFromCache(handle, id, read, objects)); + UNSCOPED_INFO("one record skipped"); + CHECK(read.size() == written.size() - 1); for (const auto& [local_id, entry] : read) { - ensureSameOverride(written.at(local_id), entry); + CAPTURE(local_id); + requireSameOverride(written.at(local_id), entry); } // Cut the last record short. @@ -523,7 +546,8 @@ namespace tut out.write(bytes.data(), (std::streamsize)(bytes.size() - 3)); } LLVOCacheEntry::vocache_gltf_overrides_map_t partial; - ensure("truncated file is refused", !LLVOCache::instance().readGenericExtrasFromCache(handle, id, partial, objects)); + UNSCOPED_INFO("truncated file is refused"); + CHECK_FALSE(LLVOCache::instance().readGenericExtrasFromCache(handle, id, partial, objects)); // An earlier format is refused by its version line alone. { @@ -531,15 +555,14 @@ namespace tut out << LLGLTFOverrideCacheEntry::VERSION_LABEL << ":1\n" << id << "\n0000000000\n"; } LLVOCacheEntry::vocache_gltf_overrides_map_t old; - ensure("old version is refused", !LLVOCache::instance().readGenericExtrasFromCache(handle, id, old, objects)); - ensure("old version loads nothing", old.empty()); + UNSCOPED_INFO("old version is refused"); + CHECK_FALSE(LLVOCache::instance().readGenericExtrasFromCache(handle, id, old, objects)); + UNSCOPED_INFO("old version loads nothing"); + CHECK(old.empty()); } - template<> template<> - void vocacheTestObject::test<8>() + TEST_CASE_METHOD(vocacheTest, "LLVOCache: memoryAdjustFactor bounds follow RAM and stay under the heap cap", "[LLVOCache]") { - set_test_name("scene load memory factor: bounds from RAM, under the heap cap, always ordered"); - auto factor = [](F32 allocated, F32 physical, F32 cap, F32 low, F32 high) { return LLVOCacheEntry::memoryAdjustFactor(allocated, physical, cap, low, high); @@ -549,47 +572,62 @@ namespace tut // 8 GB with the default settings: the bounds are the RAM fractions, // 2048 and 4915, and the factor runs from 1 to 0 between them - ensure("at the low bound nothing is pulled in", close_to(factor(2048.f, 8192.f, no_cap, 750.f, 2048.f), 1.f)); - ensure("at the high bound everything is", close_to(factor(4915.2f, 8192.f, no_cap, 750.f, 2048.f), 0.f)); - ensure("halfway is half", close_to(factor(3481.6f, 8192.f, no_cap, 750.f, 2048.f), 0.5f)); - ensure("well below the low bound is still 1", close_to(factor(100.f, 8192.f, no_cap, 750.f, 2048.f), 1.f)); + UNSCOPED_INFO("at the low bound nothing is pulled in"); + CHECK(close_to(factor(2048.f, 8192.f, no_cap, 750.f, 2048.f), 1.f)); + UNSCOPED_INFO("at the high bound everything is"); + CHECK(close_to(factor(4915.2f, 8192.f, no_cap, 750.f, 2048.f), 0.f)); + UNSCOPED_INFO("halfway is half"); + CHECK(close_to(factor(3481.6f, 8192.f, no_cap, 750.f, 2048.f), 0.5f)); + UNSCOPED_INFO("well below the low bound is still 1"); + CHECK(close_to(factor(100.f, 8192.f, no_cap, 750.f, 2048.f), 1.f)); // no RAM figure: the settings alone - ensure("without a RAM figure the settings are the bounds", close_to(factor(1399.f, 0.f, no_cap, 750.f, 2048.f), 0.5f)); + UNSCOPED_INFO("without a RAM figure the settings are the bounds"); + CHECK(close_to(factor(1399.f, 0.f, no_cap, 750.f, 2048.f), 0.5f)); // 64 GB behind a 16 GB heap cap: the tightest point moves under the // cap and the loose point keeps its ratio to it const F32 cap = 16384.f; - ensure("the tightest point sits under the cap", close_to(factor(cap * 0.9f, 65536.f, cap, 750.f, 2048.f), 0.f)); - ensure("the loose point scaled with it", close_to(factor(6144.f, 65536.f, cap, 750.f, 2048.f), 1.f)); - ensure("between them is between", close_to(factor(10444.8f, 65536.f, cap, 750.f, 2048.f), 0.5f)); - ensure("past the cap is still a factor", factor(2.f * cap, 65536.f, cap, 750.f, 2048.f) == 0.f); + UNSCOPED_INFO("the tightest point sits under the cap"); + CHECK(close_to(factor(cap * 0.9f, 65536.f, cap, 750.f, 2048.f), 0.f)); + UNSCOPED_INFO("the loose point scaled with it"); + CHECK(close_to(factor(6144.f, 65536.f, cap, 750.f, 2048.f), 1.f)); + UNSCOPED_INFO("between them is between"); + CHECK(close_to(factor(10444.8f, 65536.f, cap, 750.f, 2048.f), 0.5f)); + UNSCOPED_INFO("past the cap is still a factor"); + CHECK(factor(2.f * cap, 65536.f, cap, 750.f, 2048.f) == 0.f); // settings the wrong way round for (F32 allocated : { 0.f, 2000.f, 2500.f, 3000.f, 4000.f }) { const F32 f = factor(allocated, 0.f, no_cap, 3000.f, 2048.f); - ensure("inverted settings still give a factor in [0, 1]", f >= 0.f && f <= 1.f); + CAPTURE(allocated); + { + INFO("inverted settings still give a factor in [0, 1]"); + REQUIRE(f >= 0.f); + REQUIRE(f <= 1.f); + } } - ensure("inverted settings: below the low setting nothing is pulled in", close_to(factor(2000.f, 0.f, no_cap, 3000.f, 2048.f), 1.f)); + UNSCOPED_INFO("inverted settings: below the low setting nothing is pulled in"); + CHECK(close_to(factor(2000.f, 0.f, no_cap, 3000.f, 2048.f), 1.f)); // a bound set by hand is the bound, tighter than RAM would make it too auto explicit_factor = [](F32 allocated, F32 physical, F32 cap, F32 low, F32 high) { return LLVOCacheEntry::memoryAdjustFactor(allocated, physical, cap, low, high, true, true); }; - ensure("explicit bounds on a big machine: at the low one nothing is pulled in", close_to(explicit_factor(1000.f, 65536.f, no_cap, 1000.f, 2000.f), 1.f)); - ensure("explicit bounds on a big machine: at the high one everything is", close_to(explicit_factor(2000.f, 65536.f, no_cap, 1000.f, 2000.f), 0.f)); - ensure("explicit bounds on a big machine: halfway is half", close_to(explicit_factor(1500.f, 65536.f, no_cap, 1000.f, 2000.f), 0.5f)); - ensure("an explicit high with a default low still comes out ordered", - LLVOCacheEntry::memoryAdjustFactor(16000.f, 65536.f, no_cap, 750.f, 2048.f, false, true) >= 0.f); + UNSCOPED_INFO("explicit bounds on a big machine: at the low one nothing is pulled in"); + CHECK(close_to(explicit_factor(1000.f, 65536.f, no_cap, 1000.f, 2000.f), 1.f)); + UNSCOPED_INFO("explicit bounds on a big machine: at the high one everything is"); + CHECK(close_to(explicit_factor(2000.f, 65536.f, no_cap, 1000.f, 2000.f), 0.f)); + UNSCOPED_INFO("explicit bounds on a big machine: halfway is half"); + CHECK(close_to(explicit_factor(1500.f, 65536.f, no_cap, 1000.f, 2000.f), 0.5f)); + UNSCOPED_INFO("an explicit high with a default low still comes out ordered"); + CHECK(LLVOCacheEntry::memoryAdjustFactor(16000.f, 65536.f, no_cap, 750.f, 2048.f, false, true) >= 0.f); } - template<> template<> - void vocacheTestObject::test<9>() + TEST_CASE_METHOD(vocacheTest, "LLVOCache: hostile extras records meet a depth cap and a budget and lose bad faces", "[LLVOCache]") { - set_test_name("hostile extras records: a depth cap, a budget for unreadable ones, and no face out of range"); - const U64 handle = to_region_handle(1006 * 256, 1000 * 256); const LLUUID id = LLUUID::generateNewID(); LLVOCacheEntry::vocache_entry_map_t objects = makeEntries(4); @@ -604,41 +642,49 @@ namespace tut writeExtrasFile(handle, id, { binaryRecord(shallow), binaryRecord(deep) }); LLVOCacheEntry::vocache_gltf_overrides_map_t overrides; - ensure("a file with one bad record is still a cache", LLVOCache::instance().readGenericExtrasFromCache(handle, id, overrides, objects)); - ensure_equals("the shallow record loaded, the deep one did not", overrides.size(), 1u); - ensure("it is the shallow one", overrides.find(1) != overrides.end()); + UNSCOPED_INFO("a file with one bad record is still a cache"); + REQUIRE(LLVOCache::instance().readGenericExtrasFromCache(handle, id, overrides, objects)); + UNSCOPED_INFO("the shallow record loaded, the deep one did not"); + CHECK(overrides.size() == 1u); + UNSCOPED_INFO("it is the shallow one"); + CHECK(overrides.find(1) != overrides.end()); // up to the budget, unreadable records are skipped; past it the file is refused std::vector garbage(16, std::string("\xff\xfe\xfd\xfc", 4)); garbage.push_back(binaryRecord(makeOverrideLLSD(3, handle, LLUUID::generateNewID(), 1))); writeExtrasFile(handle, id, garbage); overrides.clear(); - ensure("sixteen unreadable records are within the budget", LLVOCache::instance().readGenericExtrasFromCache(handle, id, overrides, objects)); - ensure_equals("and the good record after them loaded", overrides.size(), 1u); + UNSCOPED_INFO("sixteen unreadable records are within the budget"); + CHECK(LLVOCache::instance().readGenericExtrasFromCache(handle, id, overrides, objects)); + UNSCOPED_INFO("and the good record after them loaded"); + CHECK(overrides.size() == 1u); garbage.insert(garbage.begin(), std::string("\xff\xfe\xfd\xfc", 4)); writeExtrasFile(handle, id, garbage); overrides.clear(); - ensure("seventeen is a bad file", !LLVOCache::instance().readGenericExtrasFromCache(handle, id, overrides, objects)); + UNSCOPED_INFO("seventeen is a bad file"); + CHECK_FALSE(LLVOCache::instance().readGenericExtrasFromCache(handle, id, overrides, objects)); // a face index no object has is dropped by the record parser LLSD faces = makeOverrideLLSD(4, handle, LLUUID::generateNewID(), 2); faces["sides"][1] = 300; LLGLTFOverrideCacheEntry entry; - ensure("the record still parses", entry.fromLLSD(faces)); - ensure_equals("only the face in range is kept", entry.mSides.size(), 1u); - ensure("and it is face 0", entry.mSides.find(0) != entry.mSides.end()); + UNSCOPED_INFO("the record still parses"); + REQUIRE(entry.fromLLSD(faces)); + UNSCOPED_INFO("only the face in range is kept"); + CHECK(entry.mSides.size() == 1u); + UNSCOPED_INFO("and it is face 0"); + CHECK(entry.mSides.find(0) != entry.mSides.end()); faces["sides"][1] = -1; LLGLTFOverrideCacheEntry negative; - ensure("a negative face parses", negative.fromLLSD(faces)); - ensure_equals("and is dropped too", negative.mSides.size(), 1u); + UNSCOPED_INFO("a negative face parses"); + REQUIRE(negative.fromLLSD(faces)); + UNSCOPED_INFO("and is dropped too"); + CHECK(negative.mSides.size() == 1u); } - template<> template<> - void vocacheTestObject::test<10>() + TEST_CASE_METHOD(vocacheTest, "LLVOCache: header slots survive a removal followed by a crash", "[LLVOCache]") { - set_test_name("header slots survive a removal followed by a crash"); - const LLUUID id = LLUUID::generateNewID(); const U64 first = to_region_handle(1010 * 256, 1000 * 256); const U64 second = to_region_handle(1011 * 256, 1000 * 256); @@ -652,33 +698,35 @@ namespace tut // crash means nobody closes it up LLVOCache::instance().removeEntry(second); restartAsAfterCrash(); - ensure_equals("two regions come back", LLVOCache::instance().getCacheEntries(), 2u); + UNSCOPED_INFO("two regions come back"); + CHECK(LLVOCache::instance().getCacheEntries() == 2u); // a new region has to take the hole, not the slot of the one behind it LLVOCache::instance().writeToCache(fourth, id, makeEntries(3), true, false); restartAsAfterCrash(); - ensure_equals("three regions come back", LLVOCache::instance().getCacheEntries(), 3u); + UNSCOPED_INFO("three regions come back"); + CHECK(LLVOCache::instance().getCacheEntries() == 3u); LLVOCacheEntry::vocache_entry_map_t read; - ensure("the first region is still there", LLVOCache::instance().readFromCache(first, id, read)); + UNSCOPED_INFO("the first region is still there"); + CHECK(LLVOCache::instance().readFromCache(first, id, read)); read.clear(); - ensure("the third region was not overwritten", LLVOCache::instance().readFromCache(third, id, read)); + UNSCOPED_INFO("the third region was not overwritten"); + CHECK(LLVOCache::instance().readFromCache(third, id, read)); read.clear(); - ensure("the new region is there", LLVOCache::instance().readFromCache(fourth, id, read)); + UNSCOPED_INFO("the new region is there"); + CHECK(LLVOCache::instance().readFromCache(fourth, id, read)); } - template<> template<> - void vocacheTestObject::test<11>() + TEST_CASE_METHOD(vocacheTest, "LLVOCache: extras are only rewritten when the overrides changed", "[LLVOCache]") { - set_test_name("extras are only rewritten when the overrides changed"); - const U64 handle = to_region_handle(1020 * 256, 1000 * 256); const LLUUID id = LLUUID::generateNewID(); LLVOCacheEntry::vocache_entry_map_t objects = makeEntries(4); LLVOCache::instance().writeToCache(handle, id, objects, true, false); LLVOCacheEntry::vocache_gltf_overrides_map_t overrides; - overrides[1] = makeOverride(1, handle, 2); - overrides[2] = makeOverride(2, handle, 3); + overrides[1] = requireOverride(1, handle, 2); + overrides[2] = requireOverride(2, handle, 3); LLVOCache::instance().writeGenericExtrasToCache(handle, id, overrides, true, false); // a save with nothing dirty leaves the file as it was, whatever it is handed @@ -686,17 +734,16 @@ namespace tut LLVOCache::instance().writeGenericExtrasToCache(handle, id, nothing, false, false); LLVOCacheEntry::vocache_gltf_overrides_map_t read; - ensure("the file still reads", LLVOCache::instance().readGenericExtrasFromCache(handle, id, read, objects)); - ensure_equals("and still holds both overrides", read.size(), 2u); - ensureSameOverride(overrides[1], read[1]); - ensureSameOverride(overrides[2], read[2]); + UNSCOPED_INFO("the file still reads"); + REQUIRE(LLVOCache::instance().readGenericExtrasFromCache(handle, id, read, objects)); + UNSCOPED_INFO("and still holds both overrides"); + REQUIRE(read.size() == 2u); + requireSameOverride(overrides[1], read[1]); + requireSameOverride(overrides[2], read[2]); } - template<> template<> - void vocacheTestObject::test<12>() + TEST_CASE_METHOD(vocacheTest, "LLVOCache: staysInMemory keeps entries seen lately or inside the rear sphere", "[LLVOCache]") { - set_test_name("an entry stays in memory while its group was seen, its frame window holds, or it is inside the rear sphere"); - // A stand-in for an entry that nothing has looked at in a while: its // group was last visible well outside the window, and it sits far // enough back that the sphere cannot reach it either. @@ -716,16 +763,16 @@ namespace tut return facts; }; - ensure("an entry nothing has seen, far behind the camera, is dropped", - !LLVOCacheEntry::staysInMemory(forgotten())); + UNSCOPED_INFO("an entry nothing has seen, far behind the camera, is dropped"); + CHECK_FALSE(LLVOCacheEntry::staysInMemory(forgotten())); // the group is the first answer, and it outranks distance entirely { LLVOCacheEntry::VisibilityFacts facts = forgotten(); facts.mGroupRecentlyVisible = true; facts.mDistanceSquared = 1e12f; - ensure("a recently visible group keeps an entry at any distance", - LLVOCacheEntry::staysInMemory(facts)); + UNSCOPED_INFO("a recently visible group keeps an entry at any distance"); + CHECK(LLVOCacheEntry::staysInMemory(facts)); } // the frame window, with no group to ask @@ -734,12 +781,12 @@ namespace tut facts.mHasGroup = false; facts.mGroupAnyVisibleFrame = 0; facts.mEntryVisibleFrame = NOW - ((S32)RANGE - 1); - ensure("inside the frame window a group-less entry is kept", - LLVOCacheEntry::staysInMemory(facts)); + UNSCOPED_INFO("inside the frame window a group-less entry is kept"); + CHECK(LLVOCacheEntry::staysInMemory(facts)); facts.mEntryVisibleFrame = NOW - ((S32)RANGE + 1); - ensure("past the frame window and past the sphere it is dropped", - !LLVOCacheEntry::staysInMemory(facts)); + UNSCOPED_INFO("past the frame window and past the sphere it is dropped"); + CHECK_FALSE(LLVOCacheEntry::staysInMemory(facts)); } // a group that saw something more recently than the entry did wins @@ -747,24 +794,25 @@ namespace tut LLVOCacheEntry::VisibilityFacts facts = forgotten(); facts.mEntryVisibleFrame = NOW - 1000; facts.mGroupAnyVisibleFrame = NOW - ((S32)RANGE - 1); - ensure("the more recent of group and entry is the one used", - LLVOCacheEntry::staysInMemory(facts)); + UNSCOPED_INFO("the more recent of group and entry is the one used"); + CHECK(LLVOCacheEntry::staysInMemory(facts)); } // the sphere behind the camera, and the two things that switch it off { LLVOCacheEntry::VisibilityFacts facts = forgotten(); facts.mDistanceSquared = 32.f * 32.f; // inside 32 + 1 - ensure("inside the rear sphere it is kept", LLVOCacheEntry::staysInMemory(facts)); + UNSCOPED_INFO("inside the rear sphere it is kept"); + CHECK(LLVOCacheEntry::staysInMemory(facts)); facts.mIsChild = true; - ensure("a child is not judged by the sphere; it goes with its parent", - !LLVOCacheEntry::staysInMemory(facts)); + UNSCOPED_INFO("a child is not judged by the sphere; it goes with its parent"); + CHECK_FALSE(LLVOCacheEntry::staysInMemory(facts)); facts.mIsChild = false; facts.mGroupOccluded = true; - ensure("a group known to be occluded does not get the sphere either", - !LLVOCacheEntry::staysInMemory(facts)); + UNSCOPED_INFO("a group known to be occluded does not get the sphere either"); + CHECK_FALSE(LLVOCacheEntry::staysInMemory(facts)); } // the sphere's edge is exclusive, and the entry's own radius widens it @@ -772,11 +820,12 @@ namespace tut LLVOCacheEntry::VisibilityFacts facts = forgotten(); const F32 edge = facts.mDistThreshold + facts.mRadius; facts.mDistanceSquared = edge * edge; - ensure("exactly on the sphere is outside it", !LLVOCacheEntry::staysInMemory(facts)); + UNSCOPED_INFO("exactly on the sphere is outside it"); + CHECK_FALSE(LLVOCacheEntry::staysInMemory(facts)); facts.mRadius = 80.f; - ensure("a large entry reaches into the sphere from further out", - LLVOCacheEntry::staysInMemory(facts)); + UNSCOPED_INFO("a large entry reaches into the sphere from further out"); + CHECK(LLVOCacheEntry::staysInMemory(facts)); } // A zero window evicts an object that was visible this very frame. The @@ -787,19 +836,17 @@ namespace tut facts.mMinFrameRange = 0; facts.mEntryVisibleFrame = NOW; facts.mGroupAnyVisibleFrame = NOW; - ensure("a zero frame window drops an entry seen this frame", - !LLVOCacheEntry::staysInMemory(facts)); + UNSCOPED_INFO("a zero frame window drops an entry seen this frame"); + CHECK_FALSE(LLVOCacheEntry::staysInMemory(facts)); facts.mMinFrameRange = 1; - ensure("a window of one keeps it", LLVOCacheEntry::staysInMemory(facts)); + UNSCOPED_INFO("a window of one keeps it"); + CHECK(LLVOCacheEntry::staysInMemory(facts)); } } - template<> template<> - void vocacheTestObject::test<13>() + TEST_CASE_METHOD(vocacheTest, "LLVOCache: the radius that evicts an entry is wider than the one that loads it", "[LLVOCache]") { - set_test_name("the radius that evicts an entry is wider than the one that loads it"); - const F32 MIN_RADIUS = 32.f; // SceneLoadMinRadius const F32 DRAW = 128.f; auto evict = [&](F32 rear_far) { return LLVOCacheEntry::evictRadius(rear_far, MIN_RADIUS, DRAW); }; @@ -811,36 +858,41 @@ namespace tut for (F32 rear_far : rear_radii) { const F32 radius = evict(rear_far); - ensure("the eviction radius clears the load radius", radius >= rear_far * 1.25f); - ensure("and never reaches inside SceneLoadMinRadius", radius >= llmin(MIN_RADIUS, DRAW)); - ensure("and does not shrink as the load radius grows", radius >= previous); + CAPTURE(rear_far); + UNSCOPED_INFO("the eviction radius clears the load radius"); + REQUIRE(radius >= rear_far * 1.25f); + UNSCOPED_INFO("and never reaches inside SceneLoadMinRadius"); + REQUIRE(radius >= llmin(MIN_RADIUS, DRAW)); + UNSCOPED_INFO("and does not shrink as the load radius grows"); + REQUIRE(radius >= previous); previous = radius; } // a draw distance under the setting is the floor instead - ensure("a short draw distance floors the radius, not the setting", - LLVOCacheEntry::evictRadius(2.f, MIN_RADIUS, 16.f) == 16.f); + UNSCOPED_INFO("a short draw distance floors the radius, not the setting"); + CHECK(LLVOCacheEntry::evictRadius(2.f, MIN_RADIUS, 16.f) == 16.f); // The sphere is the whole sphere, not just the part behind the camera, // and nothing beyond the draw distance is drawn, so nothing beyond it is // worth keeping for the turn back. Above four fifths of draw distance the // band between the two radii narrows to what is left. - ensure("the draw distance caps the radius", - LLVOCacheEntry::evictRadius(120.f, MIN_RADIUS, DRAW) == DRAW); + UNSCOPED_INFO("the draw distance caps the radius"); + CHECK(LLVOCacheEntry::evictRadius(120.f, MIN_RADIUS, DRAW) == DRAW); // The invariant the two radii exist for: an entry sitting exactly at the // distance the back cull preloads it from is not evictable, however long // it has been out of sight. Turning around cannot cross both at once. for (F32 rear_far : rear_radii) { + CAPTURE(rear_far); LLVOCacheEntry::VisibilityFacts facts; facts.mCurrentFrame = 5000; facts.mEntryVisibleFrame = 0; facts.mMinFrameRange = 64; facts.mDistanceSquared = rear_far * rear_far; facts.mDistThreshold = evict(rear_far); - ensure("an entry at the load radius is never evicted at the eviction radius", - LLVOCacheEntry::staysInMemory(facts)); + UNSCOPED_INFO("an entry at the load radius is never evicted at the eviction radius"); + REQUIRE(LLVOCacheEntry::staysInMemory(facts)); } } } diff --git a/indra/newview/tests/llworldmap_test.cpp b/indra/newview/tests/llworldmap_test.cpp index 655ebb5052..76a04d4e8f 100644 --- a/indra/newview/tests/llworldmap_test.cpp +++ b/indra/newview/tests/llworldmap_test.cpp @@ -37,7 +37,7 @@ // Class to test #include "../llworldmap.h" // Tut header -#include "../test/lltut.h" +#include // ------------------------------------------------------------------------------------------- // Stubbing: Declarations required to link and run the class being tested @@ -106,7 +106,7 @@ const std::string TOOLTIP_TEST = "Tooltip Foo"; const std::string SIM_NAME_TEST = "Sim Foo"; -namespace tut +namespace { // Test wrapper declarations struct iteminfo_test @@ -159,19 +159,6 @@ namespace tut } }; - // Tut templating thingamagic: test group, object and test instance - typedef test_group iteminfo_t; - typedef iteminfo_t::object iteminfo_object_t; - tut::iteminfo_t tut_iteminfo("LLItemInfo"); - - typedef test_group siminfo_t; - typedef siminfo_t::object siminfo_object_t; - tut::siminfo_t tut_siminfo("LLSimInfo"); - - typedef test_group worldmap_t; - typedef worldmap_t::object worldmap_object_t; - tut::worldmap_t tut_worldmap("LLWorldMap"); - // --------------------------------------------------------------------------------------- // Test functions // Notes: @@ -183,86 +170,84 @@ namespace tut // --------------------------------------------------------------------------------------- // Test the LLItemInfo interface // --------------------------------------------------------------------------------------- - template<> template<> - void iteminfo_object_t::test<1>() + TEST_CASE_METHOD(iteminfo_test, "LLItemInfo: each accessor returns what the item was made or set with", "[LLItemInfo]") { // Test 1 : setCount() / getCount() mItem->setCount(10); - ensure("LLItemInfo::setCount() test failed", mItem->getCount() == 10); + CHECK(mItem->getCount() == 10); // Test 2 : setTooltip() / getToolTip() std::string tooltip = TOOLTIP_TEST; mItem->setTooltip(tooltip); - ensure("LLItemInfo::setTooltip() test failed", mItem->getToolTip() == TOOLTIP_TEST); + CHECK(mItem->getToolTip() == TOOLTIP_TEST); // Test 3 : setElevation() / getGlobalPosition() mItem->setElevation(Z_WORLD_TEST); LLVector3d pos = mItem->getGlobalPosition(); LLVector3d ref(X_WORLD_TEST, Y_WORLD_TEST, Z_WORLD_TEST); - ensure("LLItemInfo::getGlobalPosition() test failed", pos == ref); + CHECK(pos == ref); // Test 4 : getName() std::string name = mItem->getName(); - ensure("LLItemInfo::getName() test failed", name == ITEM_NAME_TEST); + CHECK(name == ITEM_NAME_TEST); // Test 5 : isName() - ensure("LLItemInfo::isName() test failed", mItem->isName(name)); + CHECK(mItem->isName(name)); // Test 6 : getUUID() LLUUID id; - ensure("LLItemInfo::getUUID() test failed", mItem->getUUID() == id); + CHECK(mItem->getUUID() == id); // Test 7 : getRegionHandle() U64 handle = to_region_handle_global(X_WORLD_TEST, Y_WORLD_TEST); - ensure("LLItemInfo::getRegionHandle() test failed", mItem->getRegionHandle() == handle); + CHECK(mItem->getRegionHandle() == handle); } // --------------------------------------------------------------------------------------- // Test the LLSimInfo interface // --------------------------------------------------------------------------------------- // Test Setters and Accessors methods - template<> template<> - void siminfo_object_t::test<1>() + TEST_CASE_METHOD(siminfo_test, "LLSimInfo: each accessor returns what was set and image calls do not throw", "[LLSimInfo]") { // Test 1 : setName() / getName() std::string name = SIM_NAME_TEST; mSim->setName(name); - ensure("LLSimInfo::setName() test failed", mSim->getName() == SIM_NAME_TEST); + CHECK(mSim->getName() == SIM_NAME_TEST); // Test 2 : isName() - ensure("LLSimInfo::isName() test failed", mSim->isName(name)); + CHECK(mSim->isName(name)); // Test 3 : getGlobalPos() LLVector3 local; LLVector3d ref(X_WORLD_TEST, Y_WORLD_TEST, 0.0f); LLVector3d pos = mSim->getGlobalPos(local); - ensure("LLSimInfo::getGlobalPos() test failed", pos == ref); + CHECK(pos == ref); // Test 4 : getGlobalOrigin() pos = mSim->getGlobalOrigin(); - ensure("LLSimInfo::getGlobalOrigin() test failed", pos == ref); + CHECK(pos == ref); // Test 5 : clearImage() try { mSim->clearImage(); } catch (...) { - fail("LLSimInfo::clearImage() test failed"); + FAIL("LLSimInfo::clearImage() threw"); } // Test 6 : dropImagePriority() try { mSim->dropImagePriority(); } catch (...) { - fail("LLSimInfo::dropImagePriority() test failed"); + FAIL("LLSimInfo::dropImagePriority() threw"); } // Test 7 : updateAgentCount() try { mSim->updateAgentCount(0.0f); } catch (...) { - fail("LLSimInfo::updateAgentCount() test failed"); + FAIL("LLSimInfo::updateAgentCount() threw"); } // Test 8 : getAgentCount() S32 agents = mSim->getAgentCount(); - ensure("LLSimInfo::getAgentCount() test failed", agents == 0); + CHECK(agents == 0); // Test 9 : setLandForSaleImage() / getLandForSaleImage() LLUUID id; mSim->setLandForSaleImage(id); LLPointer image = mSim->getLandForSaleImage(); - ensure("LLSimInfo::getLandForSaleImage() test failed", image.isNull()); + REQUIRE(image.isNull()); // Test 10 : isPG() mSim->setAccess(SIM_ACCESS_PG); - ensure("LLSimInfo::isPG() test failed", mSim->isPG()); + CHECK(mSim->isPG()); // Test 11 : isDown() mSim->setAccess(SIM_ACCESS_DOWN); - ensure("LLSimInfo::isDown() test failed", mSim->isDown()); + CHECK(mSim->isDown()); // Test 12 : Access strings can't be accessed from unit test... //ensure("LLSimInfo::getAccessString() test failed", mSim->getAccessString() == "Offline"); // Test 13 : Region strings can't be accessed from unit test... @@ -270,30 +255,35 @@ namespace tut //ensure("LLSimInfo::setRegionFlags() test failed", mSim->getFlagsString() == "Sandbox"); } // Test management of LLInfoItem lists - template<> template<> - void siminfo_object_t::test<2>() + TEST_CASE_METHOD(siminfo_test, "LLSimInfo: clearItems empties every item list but agent locations", "[LLSimInfo]") { // Test 14 : clearItems() try { mSim->clearItems(); } catch (...) { - fail("LLSimInfo::clearItems() at init test failed"); + FAIL("LLSimInfo::clearItems() threw at init"); } // Test 15 : Verify that all the lists are empty LLSimInfo::item_info_list_t list; list = mSim->getTeleHub(); - ensure("LLSimInfo::getTeleHub() empty at init test failed", list.empty()); + UNSCOPED_INFO("telehubs"); + REQUIRE(list.empty()); list = mSim->getInfoHub(); - ensure("LLSimInfo::getInfoHub() empty at init test failed", list.empty()); + UNSCOPED_INFO("infohubs"); + REQUIRE(list.empty()); list = mSim->getPGEvent(); - ensure("LLSimInfo::getPGEvent() empty at init test failed", list.empty()); + UNSCOPED_INFO("PG events"); + REQUIRE(list.empty()); list = mSim->getMatureEvent(); - ensure("LLSimInfo::getMatureEvent() empty at init test failed", list.empty()); + UNSCOPED_INFO("mature events"); + REQUIRE(list.empty()); list = mSim->getLandForSale(); - ensure("LLSimInfo::getLandForSale() empty at init test failed", list.empty()); + UNSCOPED_INFO("land for sale"); + REQUIRE(list.empty()); list = mSim->getAgentLocation(); - ensure("LLSimInfo::getAgentLocation() empty at init test failed", list.empty()); + UNSCOPED_INFO("agent locations"); + REQUIRE(list.empty()); // Create an item to be inserted LLUUID id; @@ -309,106 +299,116 @@ namespace tut // Test 16 : Verify that the lists contain 1 item each list = mSim->getTeleHub(); - ensure("LLSimInfo::insertTeleHub() test failed", list.size() == 1); + UNSCOPED_INFO("telehubs"); + REQUIRE(list.size() == 1); list = mSim->getInfoHub(); - ensure("LLSimInfo::insertInfoHub() test failed", list.size() == 1); + UNSCOPED_INFO("infohubs"); + REQUIRE(list.size() == 1); list = mSim->getPGEvent(); - ensure("LLSimInfo::insertPGEvent() test failed", list.size() == 1); + UNSCOPED_INFO("PG events"); + REQUIRE(list.size() == 1); list = mSim->getMatureEvent(); - ensure("LLSimInfo::insertMatureEvent() test failed", list.size() == 1); + UNSCOPED_INFO("mature events"); + REQUIRE(list.size() == 1); list = mSim->getLandForSale(); - ensure("LLSimInfo::insertLandForSale() test failed", list.size() == 1); + UNSCOPED_INFO("land for sale"); + REQUIRE(list.size() == 1); list = mSim->getAgentLocation(); - ensure("LLSimInfo::insertAgentLocation() test failed", list.size() == 1); + UNSCOPED_INFO("agent locations"); + REQUIRE(list.size() == 1); // Test 17 : clearItems() try { mSim->clearItems(); } catch (...) { - fail("LLSimInfo::clearItems() at end test failed"); + FAIL("LLSimInfo::clearItems() threw at end"); } // Test 18 : Verify that all the lists are empty again... *except* agent which is persisted!! (on purpose) list = mSim->getTeleHub(); - ensure("LLSimInfo::getTeleHub() empty after clear test failed", list.empty()); + UNSCOPED_INFO("telehubs"); + REQUIRE(list.empty()); list = mSim->getInfoHub(); - ensure("LLSimInfo::getInfoHub() empty after clear test failed", list.empty()); + UNSCOPED_INFO("infohubs"); + REQUIRE(list.empty()); list = mSim->getPGEvent(); - ensure("LLSimInfo::getPGEvent() empty after clear test failed", list.empty()); + UNSCOPED_INFO("PG events"); + REQUIRE(list.empty()); list = mSim->getMatureEvent(); - ensure("LLSimInfo::getMatureEvent() empty after clear test failed", list.empty()); + UNSCOPED_INFO("mature events"); + REQUIRE(list.empty()); list = mSim->getLandForSale(); - ensure("LLSimInfo::getLandForSale() empty after clear test failed", list.empty()); + UNSCOPED_INFO("land for sale"); + REQUIRE(list.empty()); list = mSim->getAgentLocation(); - ensure("LLSimInfo::getAgentLocation() empty after clear test failed", list.size() == 1); + UNSCOPED_INFO("agent locations, which clearItems() keeps"); + REQUIRE(list.size() == 1); } // --------------------------------------------------------------------------------------- // Test the LLWorldMap interface // --------------------------------------------------------------------------------------- // Test Setters and Accessors methods - template<> template<> - void worldmap_object_t::test<1>() + TEST_CASE_METHOD(worldmap_test, "LLWorldMap: the image and region calls do not throw and no tile is loaded", "[LLWorldMap]") { // Test 1 : reset() try { mWorld->reset(); } catch (...) { - fail("LLWorldMap::reset() at init test failed"); + FAIL("LLWorldMap::reset() threw at init"); } // Test 2 : clearImageRefs() try { mWorld->clearImageRefs(); } catch (...) { - fail("LLWorldMap::clearImageRefs() test failed"); + FAIL("LLWorldMap::clearImageRefs() threw"); } // Test 3 : dropImagePriorities() try { mWorld->dropImagePriorities(); } catch (...) { - fail("LLWorldMap::dropImagePriorities() test failed"); + FAIL("LLWorldMap::dropImagePriorities() threw"); } // Test 4 : reloadItems() try { mWorld->reloadItems(true); } catch (...) { - fail("LLWorldMap::reloadItems() test failed"); + FAIL("LLWorldMap::reloadItems() threw"); } // Test 5 : updateRegions() try { mWorld->updateRegions(1000, 1000, 1004, 1004); } catch (...) { - fail("LLWorldMap::updateRegions() test failed"); + FAIL("LLWorldMap::updateRegions() threw"); } // Test 6 : equalizeBoostLevels() try { mWorld->equalizeBoostLevels(); } catch (...) { - fail("LLWorldMap::equalizeBoostLevels() test failed"); + FAIL("LLWorldMap::equalizeBoostLevels() threw"); } // Test 7 : getObjectsTile() try { LLPointer image = mWorld->getObjectsTile((U32)(X_WORLD_TEST/REGION_WIDTH_METERS), (U32)(Y_WORLD_TEST/REGION_WIDTH_METERS), 1); - ensure("LLWorldMap::getObjectsTile() failed", image.isNull()); + CHECK(image.isNull()); } catch (...) { - fail("LLWorldMap::getObjectsTile() test failed with exception"); + FAIL("LLWorldMap::getObjectsTile() threw"); } } // Test management of LLSimInfo lists - template<> template<> - void worldmap_object_t::test<2>() + TEST_CASE_METHOD(worldmap_test, "LLWorldMap: inserted regions are found by position and name and handle until reset", "[LLWorldMap]") { // Test 8 : reset() try { mWorld->reset(); } catch (...) { - fail("LLWorldMap::reset() at init test failed"); + FAIL("LLWorldMap::reset() threw at init"); } // Test 9 : Verify that all the region list is empty LLWorldMap::sim_info_map_t list; list = mWorld->getRegionMap(); - ensure("LLWorldMap::getRegionMap() empty at init test failed", list.empty()); + REQUIRE(list.empty()); // Test 10 : Insert a region bool success; @@ -421,7 +421,11 @@ namespace tut SIM_ACCESS_PG, REGION_FLAGS_SANDBOX); list = mWorld->getRegionMap(); - ensure("LLWorldMap::insertRegion() failed", success && (list.size() == 1)); + { + INFO("insertRegion()"); + CHECK(success); + REQUIRE(list.size() == 1); + } // Test 11 : Insert an item in the same region -> number of regions doesn't increase std::string name_item = ITEM_NAME_TEST; @@ -432,7 +436,11 @@ namespace tut MAP_ITEM_LAND_FOR_SALE, 0, 0); list = mWorld->getRegionMap(); - ensure("LLWorldMap::insertItem() in existing region failed", success && (list.size() == 1)); + { + INFO("insertItem() in the same region"); + CHECK(success); + REQUIRE(list.size() == 1); + } // Test 12 : Insert an item in another region -> number of regions increases success = mWorld->insertItem( U32(X_WORLD_TEST + REGION_WIDTH_METERS*2), @@ -442,7 +450,11 @@ namespace tut MAP_ITEM_LAND_FOR_SALE, 0, 0); list = mWorld->getRegionMap(); - ensure("LLWorldMap::insertItem() in unexisting region failed", success && (list.size() == 2)); + { + INFO("insertItem() in another region"); + CHECK(success); + REQUIRE(list.size() == 2); + } // Test 13 : simInfoFromPosGlobal() in region LLVector3d pos1( X_WORLD_TEST + REGION_WIDTH_METERS*2 + REGION_WIDTH_METERS/2, @@ -450,120 +462,135 @@ namespace tut 0.0f); LLSimInfo* sim; sim = mWorld->simInfoFromPosGlobal(pos1); - ensure("LLWorldMap::simInfoFromPosGlobal() test on existing region failed", sim != NULL); + UNSCOPED_INFO("simInfoFromPosGlobal() in a region"); + REQUIRE(sim != NULL); // Test 14 : simInfoFromPosGlobal() outside region LLVector3d pos2( X_WORLD_TEST + REGION_WIDTH_METERS*4 + REGION_WIDTH_METERS/2, Y_WORLD_TEST + REGION_WIDTH_METERS*4 + REGION_WIDTH_METERS/2, 0.0f); sim = mWorld->simInfoFromPosGlobal(pos2); - ensure("LLWorldMap::simInfoFromPosGlobal() test outside region failed", sim == NULL); + UNSCOPED_INFO("simInfoFromPosGlobal() outside any region"); + REQUIRE(sim == NULL); // Test 15 : simInfoFromName() sim = mWorld->simInfoFromName(name_sim); - ensure("LLWorldMap::simInfoFromName() test on existing region failed", sim != NULL); + UNSCOPED_INFO("simInfoFromName()"); + REQUIRE(sim != NULL); // Test 16 : simInfoFromHandle() U64 handle = to_region_handle_global(X_WORLD_TEST, Y_WORLD_TEST); sim = mWorld->simInfoFromHandle(handle); - ensure("LLWorldMap::simInfoFromHandle() test on existing region failed", sim != NULL); + UNSCOPED_INFO("simInfoFromHandle()"); + REQUIRE(sim != NULL); // Test 17 : simNameFromPosGlobal() LLVector3d pos3( X_WORLD_TEST + REGION_WIDTH_METERS/2, Y_WORLD_TEST + REGION_WIDTH_METERS/2, 0.0f); success = mWorld->simNameFromPosGlobal(pos3, name_sim); - ensure("LLWorldMap::simNameFromPosGlobal() test on existing region failed", success && (name_sim == SIM_NAME_TEST)); + { + INFO("simNameFromPosGlobal() in a region"); + CHECK(success); + CHECK(name_sim == SIM_NAME_TEST); + } // Test 18 : reset() try { mWorld->reset(); } catch (...) { - fail("LLWorldMap::reset() at end test failed"); + FAIL("LLWorldMap::reset() threw at end"); } // Test 19 : Verify that all the region list is empty list = mWorld->getRegionMap(); - ensure("LLWorldMap::getRegionMap() empty at end test failed", list.empty()); + REQUIRE(list.empty()); } // Test tracking - template<> template<> - void worldmap_object_t::test<3>() + TEST_CASE_METHOD(worldmap_test, "LLWorldMap: tracking a position sets its flags and keeps it until cancelled", "[LLWorldMap]") { // Point to track LLVector3d pos( X_WORLD_TEST + REGION_WIDTH_METERS/2, Y_WORLD_TEST + REGION_WIDTH_METERS/2, Z_WORLD_TEST); // Test 20 : no tracking mWorld->cancelTracking(); - ensure("LLWorldMap::cancelTracking() at begin test failed", mWorld->isTracking() == false); + CHECK_FALSE(mWorld->isTracking()); // Test 21 : set tracking mWorld->setTracking(pos); - ensure("LLWorldMap::setTracking() failed", mWorld->isTracking() && !mWorld->isTrackingValidLocation()); + REQUIRE(mWorld->isTracking()); + CHECK_FALSE(mWorld->isTrackingValidLocation()); // Test 22 : set click and commit flags mWorld->setTrackingDoubleClick(); - ensure("LLWorldMap::setTrackingDoubleClick() failed", mWorld->isTrackingDoubleClick()); + CHECK(mWorld->isTrackingDoubleClick()); mWorld->setTrackingCommit(); - ensure("LLWorldMap::setTrackingCommit() failed", mWorld->isTrackingCommit()); + CHECK(mWorld->isTrackingCommit()); // Test 23 : in rectangle test bool inRect = mWorld->isTrackingInRectangle( X_WORLD_TEST, Y_WORLD_TEST, X_WORLD_TEST + REGION_WIDTH_METERS, Y_WORLD_TEST + REGION_WIDTH_METERS); - ensure("LLWorldMap::isTrackingInRectangle() in rectangle failed", inRect); + UNSCOPED_INFO("inside the rectangle"); + CHECK(inRect); inRect = mWorld->isTrackingInRectangle( X_WORLD_TEST + REGION_WIDTH_METERS, Y_WORLD_TEST + REGION_WIDTH_METERS, X_WORLD_TEST + 2 * REGION_WIDTH_METERS, Y_WORLD_TEST + 2 * REGION_WIDTH_METERS); - ensure("LLWorldMap::isTrackingInRectangle() outside rectangle failed", !inRect); + UNSCOPED_INFO("outside the rectangle"); + CHECK_FALSE(inRect); // Test 24 : set tracking to valid and invalid mWorld->setTrackingValid(); - ensure("LLWorldMap::setTrackingValid() failed", mWorld->isTrackingValidLocation() && !mWorld->isTrackingInvalidLocation()); + REQUIRE(mWorld->isTrackingValidLocation()); + CHECK_FALSE(mWorld->isTrackingInvalidLocation()); mWorld->setTrackingInvalid(); - ensure("LLWorldMap::setTrackingInvalid() failed", !mWorld->isTrackingValidLocation() && mWorld->isTrackingInvalidLocation()); + REQUIRE_FALSE(mWorld->isTrackingValidLocation()); + CHECK(mWorld->isTrackingInvalidLocation()); // Test 25 : getTrackedPositionGlobal() LLVector3d res = mWorld->getTrackedPositionGlobal(); - ensure("LLWorldMap::getTrackedPositionGlobal() failed", res == pos); + CHECK(res == pos); // Test 26 : reset tracking mWorld->cancelTracking(); - ensure("LLWorldMap::cancelTracking() at end test failed", mWorld->isTracking() == false); + CHECK_FALSE(mWorld->isTracking()); } // Test updateRegions() request grouping, size limits, and bounds - template<> template<> - void worldmap_object_t::test<4>() + TEST_CASE_METHOD(worldmap_test, "LLWorldMap: updateRegions asks in requests of at most 64 regions within bounds", "[LLWorldMap]") { // Test 27 : 1x1 block (4x4 = 16 regions) - single small request within bounds mWorld->reset(); gMapBlockRequests.clear(); mWorld->updateRegions(0, 0, 3, 3); - ensure("updateRegions 1x1 block: expected 1 request", gMapBlockRequests.size() == 1); + UNSCOPED_INFO("1x1 block: one request"); + REQUIRE(gMapBlockRequests.size() == 1); { + INFO("1x1 block"); const MapBlockRequest& req = gMapBlockRequests[0]; - ensure("updateRegions 1x1 block: min_x", req.min_x == 0); - ensure("updateRegions 1x1 block: min_y", req.min_y == 0); - ensure("updateRegions 1x1 block: max_x", req.max_x == 3); - ensure("updateRegions 1x1 block: max_y", req.max_y == 3); + CHECK(req.min_x == 0); + CHECK(req.min_y == 0); + CHECK(req.max_x == 3); + CHECK(req.max_y == 3); S32 regions = (req.max_x - req.min_x + 1) * (req.max_y - req.min_y + 1); - ensure("updateRegions 1x1 block: must not exceed 64 regions", regions <= 64); + CHECK(regions <= 64); } // Test 28 : 2x2 blocks (8x8 = 64 regions) - single request at the 64-region limit mWorld->reset(); gMapBlockRequests.clear(); mWorld->updateRegions(0, 0, 7, 7); - ensure("updateRegions 2x2 blocks: expected 1 request", gMapBlockRequests.size() == 1); + UNSCOPED_INFO("2x2 blocks: one request"); + REQUIRE(gMapBlockRequests.size() == 1); { + INFO("2x2 blocks"); const MapBlockRequest& req = gMapBlockRequests[0]; - ensure("updateRegions 2x2 blocks: min_x", req.min_x == 0); - ensure("updateRegions 2x2 blocks: min_y", req.min_y == 0); - ensure("updateRegions 2x2 blocks: max_x", req.max_x == 7); - ensure("updateRegions 2x2 blocks: max_y", req.max_y == 7); + CHECK(req.min_x == 0); + CHECK(req.min_y == 0); + CHECK(req.max_x == 7); + CHECK(req.max_y == 7); S32 regions = (req.max_x - req.min_x + 1) * (req.max_y - req.min_y + 1); - ensure("updateRegions 2x2 blocks: must not exceed 64 regions", regions <= 64); + CHECK(regions <= 64); } // Test 29 : 4x4 blocks (16x16 = 256 total regions) - must split into multiple requests @@ -571,24 +598,26 @@ namespace tut mWorld->reset(); gMapBlockRequests.clear(); mWorld->updateRegions(0, 0, 15, 15); - ensure("updateRegions 4x4 blocks: expected 4 requests", gMapBlockRequests.size() == 4); + UNSCOPED_INFO("4x4 blocks: split into four strips"); + REQUIRE(gMapBlockRequests.size() == 4); for (size_t i = 0; i < gMapBlockRequests.size(); ++i) { + CAPTURE(i); const MapBlockRequest& req = gMapBlockRequests[i]; S32 regions = (req.max_x - req.min_x + 1) * (req.max_y - req.min_y + 1); - ensure("updateRegions 4x4 blocks: each request must not exceed 64 regions", regions <= 64); + REQUIRE(regions <= 64); // Requests must stay within the requested area (0..15) - ensure("updateRegions 4x4 blocks: max_x in bounds", req.max_x <= 15); - ensure("updateRegions 4x4 blocks: max_y in bounds", req.max_y <= 15); + REQUIRE(req.max_x <= 15); + REQUIRE(req.max_y <= 15); } // Each request covers one horizontal strip of 4 blocks (rows 0..3, 4..7, 8..11, 12..15) - ensure("updateRegions 4x4 blocks: request[0] min_y", gMapBlockRequests[0].min_y == 0); - ensure("updateRegions 4x4 blocks: request[0] max_y", gMapBlockRequests[0].max_y == 3); - ensure("updateRegions 4x4 blocks: request[1] min_y", gMapBlockRequests[1].min_y == 4); - ensure("updateRegions 4x4 blocks: request[1] max_y", gMapBlockRequests[1].max_y == 7); - ensure("updateRegions 4x4 blocks: request[2] min_y", gMapBlockRequests[2].min_y == 8); - ensure("updateRegions 4x4 blocks: request[2] max_y", gMapBlockRequests[2].max_y == 11); - ensure("updateRegions 4x4 blocks: request[3] min_y", gMapBlockRequests[3].min_y == 12); - ensure("updateRegions 4x4 blocks: request[3] max_y", gMapBlockRequests[3].max_y == 15); + CHECK(gMapBlockRequests[0].min_y == 0); + CHECK(gMapBlockRequests[0].max_y == 3); + CHECK(gMapBlockRequests[1].min_y == 4); + CHECK(gMapBlockRequests[1].max_y == 7); + CHECK(gMapBlockRequests[2].min_y == 8); + CHECK(gMapBlockRequests[2].max_y == 11); + CHECK(gMapBlockRequests[3].min_y == 12); + CHECK(gMapBlockRequests[3].max_y == 15); } } diff --git a/indra/newview/tests/llworldmipmap_test.cpp b/indra/newview/tests/llworldmipmap_test.cpp index 76b50c4c5b..e3cc6f2237 100644 --- a/indra/newview/tests/llworldmipmap_test.cpp +++ b/indra/newview/tests/llworldmipmap_test.cpp @@ -32,7 +32,7 @@ // Class to test #include "../llworldmipmap.h" // Tut header -#include "../test/lltut.h" +#include // ------------------------------------------------------------------------------------------- // Stubbing: Declarations required to link and run the class being tested @@ -57,7 +57,7 @@ LLControlGroup gSavedSettings("llworldmipmap"); // ------------------------------------------------------------------------------------------- // TUT // ------------------------------------------------------------------------------------------- -namespace tut +namespace { // Test wrapper declaration struct worldmipmap_test @@ -81,11 +81,6 @@ namespace tut } }; - // Tut templating thingamagic: test group, object and test instance - typedef test_group worldmipmap_t; - typedef worldmipmap_t::object worldmipmap_object_t; - tut::worldmipmap_t tut_worldmipmap("LLWorldMipmap"); - // --------------------------------------------------------------------------------------- // Test functions // Notes: @@ -94,38 +89,32 @@ namespace tut // * Remember that you cannot test private methods with tut // --------------------------------------------------------------------------------------- // Test static methods - // Test 1 : scaleToLevel() - template<> template<> - void worldmipmap_object_t::test<1>() + TEST_CASE_METHOD(worldmipmap_test, "LLWorldMipmap: scaleToLevel is MAP_LEVELS at zero and 1 from a tile's size up", "[LLWorldMipmap]") { S32 level = mMap->scaleToLevel(0.0); - ensure("scaleToLevel() test 1 failed", level == LLWorldMipmap::MAP_LEVELS); + CHECK(level == LLWorldMipmap::MAP_LEVELS); level = mMap->scaleToLevel((F32)LLWorldMipmap::MAP_TILE_SIZE); - ensure("scaleToLevel() test 2 failed", level == 1); + CHECK(level == 1); level = mMap->scaleToLevel(10.f * LLWorldMipmap::MAP_TILE_SIZE); - ensure("scaleToLevel() test 3 failed", level == 1); + CHECK(level == 1); } - // Test 2 : globalToMipmap() - template<> template<> - void worldmipmap_object_t::test<2>() + TEST_CASE_METHOD(worldmipmap_test, "LLWorldMipmap: globalToMipmap gives a position's region grid cell", "[LLWorldMipmap]") { U32 grid_x, grid_y; mMap->globalToMipmap(1000.f*REGION_WIDTH_METERS, 1000.f*REGION_WIDTH_METERS, 1, &grid_x, &grid_y); - ensure("globalToMipmap() test 1 failed", (grid_x == 1000) && (grid_y == 1000)); + CHECK(grid_x == 1000); + CHECK(grid_y == 1000); mMap->globalToMipmap(0.0, 0.0, LLWorldMipmap::MAP_LEVELS, &grid_x, &grid_y); - ensure("globalToMipmap() test 2 failed", (grid_x == 0) && (grid_y == 0)); + CHECK(grid_x == 0); + CHECK(grid_y == 0); } - // Test 3 : getObjectsTile() - template<> template<> - void worldmipmap_object_t::test<3>() + TEST_CASE_METHOD(worldmipmap_test, "LLWorldMipmap: getObjectsTile (not yet tested)", "[LLWorldMipmap]") { // Depends on some inline methods in LLViewerImage... Thinking about how to make this work // LLPointer img = mMap->getObjectsTile(0, 0, 1); // ensure("getObjectsTile() test failed", img.isNull()); } - // Test 4 : equalizeBoostLevels() - template<> template<> - void worldmipmap_object_t::test<4>() + TEST_CASE_METHOD(worldmipmap_test, "LLWorldMipmap: equalizeBoostLevels does not throw", "[LLWorldMipmap]") { try { @@ -133,12 +122,10 @@ namespace tut } catch (...) { - fail("equalizeBoostLevels() test failed"); + FAIL("equalizeBoostLevels() threw"); } } - // Test 5 : dropBoostLevels() - template<> template<> - void worldmipmap_object_t::test<5>() + TEST_CASE_METHOD(worldmipmap_test, "LLWorldMipmap: dropBoostLevels does not throw", "[LLWorldMipmap]") { try { @@ -146,12 +133,10 @@ namespace tut } catch (...) { - fail("dropBoostLevels() test failed"); + FAIL("dropBoostLevels() threw"); } } - // Test 6 : reset() - template<> template<> - void worldmipmap_object_t::test<6>() + TEST_CASE_METHOD(worldmipmap_test, "LLWorldMipmap: reset does not throw", "[LLWorldMipmap]") { try { @@ -159,7 +144,7 @@ namespace tut } catch (...) { - fail("reset() test failed"); + FAIL("reset() threw"); } } } diff --git a/indra/newview/tests/lutcube_test.cpp b/indra/newview/tests/lutcube_test.cpp index b4f183c19e..ee8d42590a 100644 --- a/indra/newview/tests/lutcube_test.cpp +++ b/indra/newview/tests/lutcube_test.cpp @@ -17,13 +17,13 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include #include "../lutcube.h" #include -namespace tut +namespace { struct lutcube_data { @@ -70,56 +70,59 @@ namespace tut return (int)cube.colorCube[(size_t)index * 4 + channel]; } - // constexpr, not const: ensure_equals takes its arguments by reference, - // which would odr-use a plain static const and want a definition. + // constexpr, not const: an assertion that took its operands by + // reference would odr-use a plain static const and want a definition. static constexpr int FULL = 65535; static constexpr int MID = 32768; }; - typedef test_group lutcube_group; - typedef lutcube_group::object lutcube_object; - tut::lutcube_group lcg("LutCube"); - // The ordinary case, and the layout the uploader depends on: red varies // fastest, so entry 1 of a size-2 cube is (FULL, 0, 0). - template<> template<> - void lutcube_object::test<1>() + TEST_CASE_METHOD(lutcube_data, "LutCube: an identity cube parses with red varying fastest", "[LutCube]") { const LutCube cube = parse(identity(2)); - ensure_equals("size", cube.size, 2); - ensure_equals("entry count", (int)cube.colorCube.size(), 2 * 2 * 2 * 4); - - ensure_equals("black R", at(cube, 0, 0), 0); - ensure_equals("black G", at(cube, 0, 1), 0); - ensure_equals("black B", at(cube, 0, 2), 0); - - ensure_equals("red is next R", at(cube, 1, 0), FULL); - ensure_equals("red is next G", at(cube, 1, 1), 0); - ensure_equals("red is next B", at(cube, 1, 2), 0); - - ensure_equals("white is last R", at(cube, 7, 0), FULL); - ensure_equals("white is last G", at(cube, 7, 1), FULL); - ensure_equals("white is last B", at(cube, 7, 2), FULL); + REQUIRE(cube.size == 2); + REQUIRE((int)cube.colorCube.size() == 2 * 2 * 2 * 4); + + UNSCOPED_INFO("black R"); + CHECK(at(cube, 0, 0) == 0); + UNSCOPED_INFO("black G"); + CHECK(at(cube, 0, 1) == 0); + UNSCOPED_INFO("black B"); + CHECK(at(cube, 0, 2) == 0); + + UNSCOPED_INFO("red is next R"); + CHECK(at(cube, 1, 0) == FULL); + UNSCOPED_INFO("red is next G"); + CHECK(at(cube, 1, 1) == 0); + UNSCOPED_INFO("red is next B"); + CHECK(at(cube, 1, 2) == 0); + + UNSCOPED_INFO("white is last R"); + CHECK(at(cube, 7, 0) == FULL); + UNSCOPED_INFO("white is last G"); + CHECK(at(cube, 7, 1) == FULL); + UNSCOPED_INFO("white is last B"); + CHECK(at(cube, 7, 2) == FULL); } // Quantisation rounds rather than truncates. 0.5 * 65535 is 32767.5; // truncating biases every entry in the cube downwards by up to one step, // which on an identity LUT is a darkening for no reason at all. - template<> template<> - void lutcube_object::test<2>() + TEST_CASE_METHOD(lutcube_data, "LutCube: quantisation rounds rather than truncates", "[LutCube]") { const LutCube cube = parse(identity(3)); - ensure_equals("size", cube.size, 3); + REQUIRE(cube.size == 3); // Entry 1 is x = 1 of 2, i.e. exactly mid grey on red alone. - ensure_equals("mid rounds up", at(cube, 1, 0), MID); + UNSCOPED_INFO("mid rounds up"); + CHECK(at(cube, 1, 0) == MID); } // DOMAIN_MIN was parsed and then ignored: the original divided by the span // but never subtracted the offset. With a domain of [-1, 1], an input of 0 // is the middle of the domain and must land on mid grey -- the old code put // it at 0. - template<> template<> - void lutcube_object::test<3>() + TEST_CASE_METHOD(lutcube_data, "LutCube: DOMAIN_MIN offsets the input so the domain's centre is mid grey", "[LutCube]") { std::ostringstream text; text << "LUT_3D_SIZE 2\n"; @@ -131,18 +134,20 @@ namespace tut } const LutCube cube = parse(text.str()); - ensure_equals("size", cube.size, 2); - ensure_equals("domain centre is mid grey", at(cube, 0, 0), MID); - ensure_equals("domain centre is mid grey", at(cube, 0, 1), MID); - ensure_equals("domain centre is mid grey", at(cube, 0, 2), MID); + REQUIRE(cube.size == 2); + { + INFO("domain centre is mid grey"); + CHECK(at(cube, 0, 0) == MID); + CHECK(at(cube, 0, 1) == MID); + CHECK(at(cube, 0, 2) == MID); + } } // The one that shows up as an artefact rather than a failure. "clampTripel" // did not clamp, so 1.1 became 255 * 1.1 = 280, which wrapped to 24 on the // cast -- a blown highlight rendered as near-black speckle. Below the // domain wrapped the other way. - template<> template<> - void lutcube_object::test<4>() + TEST_CASE_METHOD(lutcube_data, "LutCube: values outside the domain clamp instead of wrapping", "[LutCube]") { std::ostringstream text; text << "LUT_3D_SIZE 2\n"; @@ -152,17 +157,19 @@ namespace tut } const LutCube cube = parse(text.str()); - ensure_equals("above domain clamps to white", at(cube, 0, 0), FULL); - ensure_equals("below domain clamps to black", at(cube, 0, 1), 0); - ensure_equals("in domain is untouched", at(cube, 0, 2), MID); + UNSCOPED_INFO("above domain clamps to white"); + CHECK(at(cube, 0, 0) == FULL); + UNSCOPED_INFO("below domain clamps to black"); + CHECK(at(cube, 0, 1) == 0); + UNSCOPED_INFO("in domain is untouched"); + CHECK(at(cube, 0, 2) == MID); } // Legal .cube files indent their data, and the original required a digit at // column zero. A rejected row does not merely lose itself: every later row // slides up into its slot, so the whole cube is wrong and the tail is left // at the full-scale fill. Indented, signed and bare-decimal rows all count. - template<> template<> - void lutcube_object::test<5>() + TEST_CASE_METHOD(lutcube_data, "LutCube: rows indented or signed or with a bare decimal point are accepted", "[LutCube]") { const LutCube plain = parse(identity(2)); @@ -178,65 +185,70 @@ namespace tut text << "1.0 1.0 1.0\n"; const LutCube indented = parse(text.str()); - ensure_equals("indented cube is accepted", indented.size, 2); - ensure("indented cube matches the plain one", indented.colorCube == plain.colorCube); + UNSCOPED_INFO("indented cube is accepted"); + REQUIRE(indented.size == 2); + UNSCOPED_INFO("indented cube matches the plain one"); + CHECK(indented.colorCube == plain.colorCube); } // The memory-safety one. With no LUT_3D_SIZE the cube is never allocated and // size stays zero, and the original wrote the first data row into it // regardless. Rejecting the file is the whole fix; the assertion here is // that we get an empty cube back and not a crash. - template<> template<> - void lutcube_object::test<6>() + TEST_CASE_METHOD(lutcube_data, "LutCube: data before LUT_3D_SIZE is rejected without writing anywhere", "[LutCube]") { const LutCube cube = parse("0.0 0.0 0.0\n1.0 1.0 1.0\n"); - ensure("data before LUT_3D_SIZE is rejected", cube.colorCube.empty()); - ensure_equals("and leaves no size", cube.size, 0); + UNSCOPED_INFO("data before LUT_3D_SIZE is rejected"); + CHECK(cube.colorCube.empty()); + UNSCOPED_INFO("and leaves no size"); + CHECK(cube.size == 0); } // A short file used to be kept. The allocation is prefilled with full // scale, so everything past the last row read came out white -- an "almost // working" LUT that blows out the top of the image. - template<> template<> - void lutcube_object::test<7>() + TEST_CASE_METHOD(lutcube_data, "LutCube: a file short of rows is rejected", "[LutCube]") { std::string text = identity(2); text.erase(text.find_last_of('\n', text.size() - 2) + 1); const LutCube cube = parse(text); - ensure("an incomplete cube is rejected", cube.colorCube.empty()); + UNSCOPED_INFO("an incomplete cube is rejected"); + CHECK(cube.colorCube.empty()); } // stof/stoi throw, and nothing used to catch them, so a corrupt file threw // out of setupGradingLUT rather than falling back. - template<> template<> - void lutcube_object::test<8>() + TEST_CASE_METHOD(lutcube_data, "LutCube: a non-numeric size or row is rejected rather than thrown", "[LutCube]") { const LutCube bad_size = parse("LUT_3D_SIZE banana\n"); - ensure("a non-numeric size is rejected", bad_size.colorCube.empty()); + UNSCOPED_INFO("a non-numeric size is rejected"); + CHECK(bad_size.colorCube.empty()); std::ostringstream text; text << "LUT_3D_SIZE 2\n"; text << "0.0 0.0 0.0\n"; text << "0.5 what 0.5\n"; const LutCube bad_row = parse(text.str()); - ensure("a malformed row is rejected", bad_row.colorCube.empty()); + UNSCOPED_INFO("a malformed row is rejected"); + CHECK(bad_row.colorCube.empty()); } // An absurd size should be refused before it is allocated, not after. - template<> template<> - void lutcube_object::test<9>() + TEST_CASE_METHOD(lutcube_data, "LutCube: a huge or degenerate or negative size is refused", "[LutCube]") { - ensure("a huge size is rejected", parse("LUT_3D_SIZE 100000\n").colorCube.empty()); - ensure("a degenerate size is rejected", parse("LUT_3D_SIZE 1\n").colorCube.empty()); - ensure("a negative size is rejected", parse("LUT_3D_SIZE -4\n").colorCube.empty()); + UNSCOPED_INFO("a huge size is rejected"); + CHECK(parse("LUT_3D_SIZE 100000\n").colorCube.empty()); + UNSCOPED_INFO("a degenerate size is rejected"); + CHECK(parse("LUT_3D_SIZE 1\n").colorCube.empty()); + UNSCOPED_INFO("a negative size is rejected"); + CHECK(parse("LUT_3D_SIZE -4\n").colorCube.empty()); } // Values separated by runs of spaces or by tabs, and a file with CRLF line // endings read somewhere that does not strip the CR. The original split on // the first " \n" it found and lost a value to any of these. - template<> template<> - void lutcube_object::test<10>() + TEST_CASE_METHOD(lutcube_data, "LutCube: runs of spaces and tabs and CRLF endings separate values", "[LutCube]") { const LutCube plain = parse(identity(2)); @@ -252,8 +264,10 @@ namespace tut text << "1.0 1.0 1.0\r\n"; const LutCube odd = parse(text.str()); - ensure_equals("odd whitespace is accepted", odd.size, 2); - ensure("odd whitespace matches the plain cube", odd.colorCube == plain.colorCube); + UNSCOPED_INFO("odd whitespace is accepted"); + REQUIRE(odd.size == 2); + UNSCOPED_INFO("odd whitespace matches the plain cube"); + CHECK(odd.colorCube == plain.colorCube); } // A value the text can carry but the arithmetic cannot survive. Whether a @@ -261,8 +275,7 @@ namespace tut // the parser must reject them itself rather than lean on the extraction // failing: a NaN sails through llclamp (both comparisons are false) and // the cast to unsigned short is undefined behaviour. - template<> template<> - void lutcube_object::test<11>() + TEST_CASE_METHOD(lutcube_data, "LutCube: nan and inf values and a non-finite domain are rejected", "[LutCube]") { static const char* const poison[] = { "nan", "inf", "-inf" }; for (const char* value : poison) @@ -274,8 +287,8 @@ namespace tut { text << "0.0 0.0 0.0\n"; } - ensure(std::string("a ") + value + " value is rejected", - parse(text.str()).colorCube.empty()); + CAPTURE(value); + REQUIRE(parse(text.str()).colorCube.empty()); } // The domain lines go through the same reader, and a non-finite domain @@ -287,6 +300,7 @@ namespace tut { domain << "0.0 0.0 0.0\n"; } - ensure("a non-finite domain is rejected", parse(domain.str()).colorCube.empty()); + UNSCOPED_INFO("a non-finite domain is rejected"); + CHECK(parse(domain.str()).colorCube.empty()); } } diff --git a/indra/newview/tests/newview_test_checks.cpp b/indra/newview/tests/newview_test_checks.cpp new file mode 100644 index 0000000000..bac831dbc3 --- /dev/null +++ b/indra/newview/tests/newview_test_checks.cpp @@ -0,0 +1,114 @@ +/** + * @file newview_test_checks.cpp + * @brief What the viewer's tests share a process with: made before the first + * case, and watched after each. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Alchemy Viewer Source Code + * Copyright (C) 2026, Rye + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "../alrecovery.h" +#include "../alscriptstudioviewer.h" + +#include "almainloop.h" +#include "alsavehistory.h" +#include "llcontrol.h" +#include "llerrorcontrol.h" +#include "threadpool.h" +#include "workqueue.h" + +#include "alteststateguard.h" +#include "newview_test_fakes.h" + +#include +#include + +#include +#include +#include + +namespace +{ + // A pointer as text: which object, or none. Only ever printed, never + // followed, since what it points at may be gone. + std::string which(const void* p) + { + return p ? llformat("%p", p) : std::string("none"); + } + + // The names a tracked class's live instances go by, sorted. + template + std::string liveNames() + { + std::vector names; + for (const std::string& name : typename TRACKED::key_snapshot()) + { + names.push_back(name); + } + std::sort(names.begin(), names.end()); + std::string text; + for (const std::string& name : names) + { + text += (text.empty() ? "" : ", ") + name; + } + return text; + } + + class NewviewChecks : public Catch::EventListenerBase + { + public: + using Catch::EventListenerBase::EventListenerBase; + + void testRunStarting(Catch::TestRunInfo const&) override + { + // The queue a case chooses to stand in for the main loop's, made + // before the first case and kept for the run: work handed back to + // it for a case gone finds it still there. Under a name of its + // own, so being made chooses nothing. + newview_test::mainLoop(); + + // llcommon's checks, without its event pumps made afresh for each + // case: the viewer's own event APIs, LLViewerControlListener's + // among them, listen on the pumps from before main, as they do for + // the viewer's whole run. A case that starts worker threads stops + // them before it ends; one that turns logging up or down puts it + // back. + ll_test::StateGuard::add("the live thread pools", liveNames); + ll_test::StateGuard::add("the live work queues", liveNames); + ll_test::StateGuard::add("LLError's default level", + [] { return std::to_string(LLError::getDefaultLevel()); }); + // A case that chooses a main loop lets go of the choice: every + // case after it would hand its work back to it. + ll_test::StateGuard::add("the main loop's queue", [] { return which(ALMainLoop::queue().get()); }); + // A case that makes a settings group of its own lets it go: one + // left would answer for that name in every case after it. + ll_test::StateGuard::add("the settings groups", liveNames); + // Script Studio's recovery, its save history and its viewer are + // each one for the process: a case that uses one of its own puts + // the one before back. + ll_test::StateGuard::add("the recovery store", [] { return which(ALRecovery::store()); }); + ll_test::StateGuard::add("the save history", [] { return which(ALRecovery::history().get()); }); + ll_test::StateGuard::add("Script Studio's viewer", [] { return which(&ALScriptStudioViewer::get()); }); + } + }; +} + +CATCH_REGISTER_LISTENER(NewviewChecks) diff --git a/indra/newview/tests/newview_test_fakes.cpp b/indra/newview/tests/newview_test_fakes.cpp index 517c4b7b71..067e080de4 100644 --- a/indra/newview/tests/newview_test_fakes.cpp +++ b/indra/newview/tests/newview_test_fakes.cpp @@ -46,6 +46,6 @@ const std::string& rlvGetAnonym(const LLAvatarName&) LL::WorkQueue& newview_test::mainLoop() { - static LL::WorkQueue queue("mainloop", 1024); + static LL::WorkQueue queue("newview_test main loop", 1024); return queue; } diff --git a/indra/newview/tests/newview_test_fakes.h b/indra/newview/tests/newview_test_fakes.h index 3c0fd0ddfb..2e2e03ae3d 100644 --- a/indra/newview/tests/newview_test_fakes.h +++ b/indra/newview/tests/newview_test_fakes.h @@ -25,6 +25,12 @@ #ifndef AL_NEWVIEW_TEST_FAKES_H #define AL_NEWVIEW_TEST_FAKES_H +#if AL_TEST_CATCH2 +#include "alrecoverystore.h" +#include "alteststateguard.h" +#include "altestteardown.h" +#endif + namespace LL { class WorkQueue; @@ -32,11 +38,33 @@ namespace LL namespace newview_test { - // The main loop's queue, as the viewer's is: made the first time a test - // asks for it, never while the process starts, and kept for the rest of - // the run, since what was posted to it for a test gone finds nobody. A - // queue's name is its key in a process, so every test shares this one. + // A queue for a test to stand in for the main loop's, chosen through + // ALMainLoop::Choice for as long as the test wants work handed back to + // it: made the first time a test asks for it, never while the process + // starts, and kept for the rest of the run, since what was posted to it + // for a test gone finds nobody. Not under the main loop's name, so that + // its being made chooses nothing for the tests after. LL::WorkQueue& mainLoop(); + +#if AL_TEST_CATCH2 + // The thread Script Studio's recovery stores write on, ALRecoveryWriter's, + // the process's, which the viewer stops in its cleanup: made by the first + // fixture that makes a store, before it does, as the store would have + // made it when each file had a process of its own, and stopped by the + // harness's teardown, so that it then runs is no change a case made. + inline void useRecoveryWriter() + { + static const bool made = [] + { + ALRecoveryWriter::getInstance(); + ll_test::Teardown::add([] { ALRecoveryWriter::deleteSingleton(); }); + ll_test::StateGuard::accept("the live thread pools"); + ll_test::StateGuard::accept("the live work queues"); + return true; + }(); + (void)made; + } +#endif } #endif // AL_NEWVIEW_TEST_FAKES_H diff --git a/indra/newview/tests/noise_test.cpp b/indra/newview/tests/noise_test.cpp index 9f5d1a4388..a5e6359ebf 100644 --- a/indra/newview/tests/noise_test.cpp +++ b/indra/newview/tests/noise_test.cpp @@ -14,13 +14,14 @@ #include "linden_common.h" -#include "../test/lltut.h" +#include +#include "altestmatchers.h" #include "../noise.h" #include -namespace tut +namespace { // Every expected value below was read off the tables as MSVC's CRT built them from // srand(42) / rand(), which is what every Windows viewer has textured terrain from. A @@ -35,29 +36,24 @@ namespace tut } }; - typedef test_group noise_group; - typedef noise_group::object noise_object; - tut::noise_group ng("noise"); - // The generator is MSVC's rand(). Where that CRT is the one linked, it is the reference. - template<> template<> - void noise_object::test<1>() + TEST_CASE_METHOD(noise_data, "noise: ALNoiseRand draws what MSVC's rand() draws", "[noise]") { #ifdef _MSC_VER srand(42); ALNoiseRand rand(42); for (int i = 0; i < 65536; ++i) { - ensure_equals("draw agrees with the CRT", rand.next(), ::rand()); + CAPTURE(i); + REQUIRE(rand.next() == ::rand()); } #else - skip("the CRT here is not the reference generator"); + SKIP("the CRT here is not the reference generator"); #endif } // The same draws, written down, for the platforms whose CRT is not the reference. - template<> template<> - void noise_object::test<2>() + TEST_CASE_METHOD(noise_data, "noise: ALNoiseRand draws MSVC's first sixteen values from seed 42", "[noise]") { static const S32 expected[16] = { 175, 400, 17869, 30056, 16083, 12879, 8016, 7644, @@ -66,13 +62,13 @@ namespace tut ALNoiseRand rand(42); for (int i = 0; i < 16; ++i) { - ensure_equals("draw", rand.next(), expected[i]); + CAPTURE(i); + REQUIRE(rand.next() == expected[i]); } } // The permutation table: its head, its whole (as a weighted sum), and its wrap. - template<> template<> - void noise_object::test<3>() + TEST_CASE_METHOD(noise_data, "noise: the permutation table's head and weighted sum match and it wraps", "[noise]") { static const S32 head[32] = { 204, 117, 7, 120, 49, 222, 28, 96, 237, 17, 30, 184, 85, 140, 181, 18, @@ -80,22 +76,24 @@ namespace tut }; for (int i = 0; i < 32; ++i) { - ensure_equals("p head", p[i], head[i]); + CAPTURE(i); + REQUIRE(p[i] == head[i]); } long long sum = 0; for (int i = 0; i < 256; ++i) { sum += (long long)p[i] * (i + 1); } - ensure_equals("p weighted sum", sum, 4318278LL); - ensure_equals("p wraps at 256", p[257], 117); - ensure_equals("p wraps at 512", p[513], 117); + CHECK(sum == 4318278LL); + UNSCOPED_INFO("p wraps at 256"); + CHECK(p[257] == 117); + UNSCOPED_INFO("p wraps at 512"); + CHECK(p[513] == 117); } // The gradient tables. g1 holds multiples of 1/256 and compares exactly; g2 and g3 are // normalised, so they get the tolerance a float sqrt needs and no more. - template<> template<> - void noise_object::test<4>() + TEST_CASE_METHOD(noise_data, "noise: the gradient tables match MSVC's in their heads", "[noise]") { static const F32 g1_head[8] = { -0.31640625f, 0.3125f, -0.37890625f, -0.94140625f, @@ -103,7 +101,8 @@ namespace tut }; for (int i = 0; i < 8; ++i) { - ensure_equals("g1 head", g1[i], g1_head[i]); + CAPTURE(i); + REQUIRE(g1[i] == g1_head[i]); } static const F32 g2_head[4][2] = { @@ -114,8 +113,9 @@ namespace tut }; for (int i = 0; i < 4; ++i) { - ensure_approximately_equals("g2 head x", g2[i][0], g2_head[i][0], 20); - ensure_approximately_equals("g2 head y", g2[i][1], g2_head[i][1], 20); + CAPTURE(i); + REQUIRE_THAT(g2[i][0], ll_test::WithinBits(g2_head[i][0], 20)); + REQUIRE_THAT(g2[i][1], ll_test::WithinBits(g2_head[i][1], 20)); } static const F32 g3_head[4][3] = { @@ -126,9 +126,10 @@ namespace tut }; for (int i = 0; i < 4; ++i) { - ensure_approximately_equals("g3 head x", g3[i][0], g3_head[i][0], 20); - ensure_approximately_equals("g3 head y", g3[i][1], g3_head[i][1], 20); - ensure_approximately_equals("g3 head z", g3[i][2], g3_head[i][2], 20); + CAPTURE(i); + REQUIRE_THAT(g3[i][0], ll_test::WithinBits(g3_head[i][0], 20)); + REQUIRE_THAT(g3[i][1], ll_test::WithinBits(g3_head[i][1], 20)); + REQUIRE_THAT(g3[i][2], ll_test::WithinBits(g3_head[i][2], 20)); } } }