From 45f68fec43ebf2e2d8573200ab05bcbe6ef82960 Mon Sep 17 00:00:00 2001 From: Rye Mutt <rye@alchemyviewer.org> Date: Mon, 4 Apr 2022 15:40:58 -0400 Subject: [PATCH] Fix some broken test builds --- indra/newview/CMakeLists.txt | 1 + indra/newview/llviewerregion.h | 2 + indra/newview/tests/lllogininstance_test.cpp | 25 +++++-- indra/newview/tests/llslurl_test.cpp | 73 ++++++++++++++++---- indra/newview/tests/llviewernetwork_test.cpp | 64 ++++++++++------- indra/newview/tests/llworldmap_test.cpp | 20 +++++- indra/newview/tests/llworldmipmap_test.cpp | 11 +++ 7 files changed, 147 insertions(+), 49 deletions(-) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index c64785e195e..4f44749afed 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -2594,6 +2594,7 @@ if (LL_TESTS) ${WINDOWS_LIBRARIES} ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} + ${LLXML_LIBRARIES} ${LLCOMMON_LIBRARIES} ${LLMESSAGE_LIBRARIES} ${LLCOREHTTP_LIBRARIES} diff --git a/indra/newview/llviewerregion.h b/indra/newview/llviewerregion.h index 27f2e19c672..3e75178c21a 100644 --- a/indra/newview/llviewerregion.h +++ b/indra/newview/llviewerregion.h @@ -31,7 +31,9 @@ // that are in to a particular region. #include <string> #include <boost/signals2.hpp> +#include <absl/container/flat_hash_set.h> +#include "xform.h" #include "llwind.h" #include "v3dmath.h" #include "llstring.h" diff --git a/indra/newview/tests/lllogininstance_test.cpp b/indra/newview/tests/lllogininstance_test.cpp index d3d9b067a9d..4c222e11aad 100644 --- a/indra/newview/tests/lllogininstance_test.cpp +++ b/indra/newview/tests/lllogininstance_test.cpp @@ -144,11 +144,11 @@ bool LLGridManager::addGrid(LLSD& grid_data) } LLGridManager::LLGridManager() : - mIsInProductionGrid(false) + mPlatform(NOPLATFORM) { } -void LLGridManager::getLoginURIs(std::vector<std::string>& uris) +void LLGridManager::getLoginURIs(std::vector<std::string>& uris) const { uris.push_back(VIEWERLOGIN_URI); } @@ -158,31 +158,42 @@ void LLGridManager::addSystemGrid(const std::string& label, const std::string& login, const std::string& helper, const std::string& login_page, + const std::string& password_url, + const std::string& register_url, const std::string& update_url_base, const std::string& web_profile_url, + const std::string& grid_status_url, + const std::string& grid_status_rss_url, + const std::string& administrator, + const std::string& platform, const std::string& login_id) { } -std::map<std::string, std::string> LLGridManager::getKnownGrids() +std::map<std::string, std::string> LLGridManager::getKnownGrids() const { std::map<std::string, std::string> result; return result; } -void LLGridManager::setGridChoice(const std::string& grid_name) +void LLGridManager::setGridChoice(const std::string&, const bool) { } -bool LLGridManager::isInProductionGrid() +bool LLGridManager::isInSecondlife() const { return false; } -std::string LLGridManager::getSLURLBase(const std::string& grid_name) +bool LLGridManager::isInOpenSim() const +{ + return false; +} + +std::string LLGridManager::getSLURLBase(const std::string& grid_name) const { return "myslurl"; } -std::string LLGridManager::getAppSLURLBase(const std::string& grid_name) +std::string LLGridManager::getAppSLURLBase(const std::string& grid_name) const { return "myappslurl"; } diff --git a/indra/newview/tests/llslurl_test.cpp b/indra/newview/tests/llslurl_test.cpp index d341f8c0c1c..30bc1ddadbb 100644 --- a/indra/newview/tests/llslurl_test.cpp +++ b/indra/newview/tests/llslurl_test.cpp @@ -29,7 +29,9 @@ #include "../llviewernetwork.h" #include "../test/lltut.h" #include "../llslurl.h" -#include "../../llxml/llcontrol.h" +#include "llxmlnode.h" +#include "llcontrol.h" +#include "llnotificationsutil.h" #include "llsdserialize.h" namespace @@ -47,6 +49,7 @@ class LLTrans { public: static std::string getString(std::string_view xml_desc, const LLStringUtil::format_map_t& args, bool def_string = false); + static void setDefaultArg(const std::string& name, std::string value); }; std::string LLTrans::getString(const std::string_view xml_desc, const LLStringUtil::format_map_t& args, bool def_string) @@ -54,6 +57,10 @@ std::string LLTrans::getString(const std::string_view xml_desc, const LLStringUt return std::string(); } +void LLTrans::setDefaultArg(const std::string& name, std::string value) +{ +} + // [RLVa:KB] - Checked: 2010-11-12 (RLVa-1.2.2a) | Added: RLVa-1.2.2a // Stub implementation to get the test to compile properly #include "../rlvhandler.h" @@ -101,6 +108,7 @@ LLControlVariable* LLControlGroup::declareString(const std::string& name, const std::string& comment, LLControlVariable::ePersist persist) {return NULL;} void LLControlGroup::setString(const std::string_view name, const std::string& val){} +LLNotificationPtr LLNotificationsUtil::add(const std::string& name, const LLSD& substitutions) { return NULL; } std::string gCmdLineLoginURI; std::string gCmdLineGridChoice; @@ -169,6 +177,19 @@ const char *gSampleGridFile = " <key>credential_type</key><string>agent</string>" " <key>grid_login_id</key><string>MyGrid</string>" " </map>" + " <key>my.stupidgrid.com:8002</key>" + " <map>" + " <key>helper_uri</key><string>https://my.stupidgrid.com/helpers/</string>" + " <key>label</key><string>My Stupid Grid</string>" + " <key>login_page</key><string>my.stupidgrid.com/loginpage</string>" + " <key>login_uri</key>" + " <array>" + " <string>my.stupidgrid.com:8002/</string>" + " </array>" + " <key>keyname</key><string>my.stupidgrid.com:8002</string>" + " <key>credential_type</key><string>agent</string>" + " <key>grid_login_id</key><string>My Stupid Grid</string>" + " </map>" " </map>" "</llsd>" ; @@ -249,26 +270,26 @@ namespace tut "http://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"); + slurl = LLSLURL("x-grid-info://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"); + "x-grid-info://my.grid.com/region/my%20region/1/2/3"); - slurl = LLSLURL("https://my.grid.com/region/my region"); + slurl = LLSLURL("x-grid-info://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"); + "x-grid-info://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"); + "x-grid-info://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"); + "x-grid-info://foo.bar.com/region/myregion/1/2/3"); slurl = LLSLURL(LLSLURL::SIM_LOCATION_HOME); ensure_equals("home", slurl.getType(), LLSLURL::HOME_LOCATION); @@ -306,7 +327,7 @@ namespace tut ensure_equals("grid4", slurl.getGrid(), "Aditi" ); LLGridManager::getInstance()->setGridChoice("my.grid.com"); - slurl = LLSLURL("https://my.grid.com/app/foo/bar?12345"); + slurl = LLSLURL("x-grid-info://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); @@ -328,12 +349,12 @@ namespace tut 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"); + "x-grid-info://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"); + "x-grid-info://my.grid.com/region/my%20region/1/2/3"); LLGridManager::getInstance()->setGridChoice("util.agni.lindenlab.com"); slurl = LLSLURL("my region", LLVector3(1,2,3)); @@ -345,10 +366,11 @@ namespace tut 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"); + "x-grid-info://my.grid.com/region/my%20region/1/2/3"); } - // Accessors + + // x-grid-location-info template<> template<> void slurlTestObject::test<3>() { @@ -359,10 +381,33 @@ namespace tut LLGridManager::getInstance()->initialize(TEST_FILENAME); LLGridManager::getInstance()->setGridChoice("my.grid.com"); - LLSLURL slurl = LLSLURL("https://my.grid.com/region/my%20region/1/2/3"); + LLSLURL slurl = LLSLURL("x-grid-info://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"); + ensure_equals("grid1", slurl.getGrid(), "my.grid.com"); + slurl = LLSLURL("x-grid-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + ensure_equals("app", slurl.getType(), LLSLURL::APP); + ensure_equals("appcmd", slurl.getAppCmd(), "agent"); + ensure_equals("apppath", slurl.getAppPath().size(), 2); + ensure_equals("apppath2", slurl.getAppPath()[0].asString(), "0e346d8b-4433-4d66-a6b0-fd37083abc4c"); + ensure_equals("apppath3", slurl.getAppPath()[1].asString(), "about"); + ensure_equals("grid1", slurl.getGrid(), "lincoln.lindenlab.com"); + LLGridManager::getInstance()->setGridChoice("my.stupidgrid.com:8002"); + slurl = LLSLURL("x-grid-info://my.stupidgrid.com:8002/app/foo/bar/baz?12345"); + ensure_equals("app", slurl.getType(), LLSLURL::APP); + ensure_equals("appcmd", slurl.getAppCmd(), "foo"); + ensure_equals("apppath", slurl.getAppPath().size(), 2); + ensure_equals("apppath2", slurl.getAppPath()[0].asString(), "bar"); + ensure_equals("apppath3", slurl.getAppPath()[1].asString(), "baz"); + ensure_equals("appquery", slurl.getAppQuery(), "12345"); + ensure_equals("grid1", slurl.getGrid(), "my.stupidgrid.com:8002"); + slurl = LLSLURL("x-grid-info://my.stupidgrid.com:8002/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("grid", slurl.getGrid(), "my.stupidgrid.com:8002"); ensure_equals("region", slurl.getRegion(), "my region"); ensure_equals("position", slurl.getPosition(), LLVector3(1, 2, 3)); diff --git a/indra/newview/tests/llviewernetwork_test.cpp b/indra/newview/tests/llviewernetwork_test.cpp index b46b9ecfc57..b6e5be60b2d 100644 --- a/indra/newview/tests/llviewernetwork_test.cpp +++ b/indra/newview/tests/llviewernetwork_test.cpp @@ -30,6 +30,7 @@ #include "../test/lltut.h" #include "../../llxml/llcontrol.h" #include "llfile.h" +#include "llnotificationsutil.h" namespace { @@ -39,6 +40,8 @@ static const char * const TEST_FILENAME("llviewernetwork_test.xml"); } +const std::string REMOTE_GRID = "http://grid.example.com:8002/"; + // // Stub implementation for LLTrans // @@ -46,6 +49,7 @@ class LLTrans { public: static std::string getString(const std::string_view xml_desc, const LLStringUtil::format_map_t& args, bool def_string = false); + static void setDefaultArg(const std::string& name, std::string value); }; std::string LLTrans::getString(const std::string_view xml_desc, const LLStringUtil::format_map_t& args, bool def_string) @@ -53,16 +57,20 @@ std::string LLTrans::getString(const std::string_view xml_desc, const LLStringUt std::string grid_label = std::string(); if(xml_desc == "AgniGridLabel") { - grid_label = "Second Life Main Grid (Agni)"; + grid_label = "Second Life"; } else if(xml_desc == "AditiGridLabel") { - grid_label = "Second Life Beta Test Grid (Aditi)"; + grid_label = "Second Life Beta"; } return grid_label; } +void LLTrans::setDefaultArg(const std::string& name, std::string value) +{ +} + //---------------------------------------------------------------------------- // Mock objects for the dependencies of the code we're testing @@ -74,6 +82,7 @@ LLControlVariable* LLControlGroup::declareString(const std::string& name, const std::string& comment, LLControlVariable::ePersist persist) {return NULL;} void LLControlGroup::setString(const std::string_view name, const std::string& val){} +LLNotificationPtr LLNotificationsUtil::add(const std::string& name, const LLSD& substitutions) { return NULL; } std::string gCmdLineLoginURI; std::string gCmdLineGridChoice; @@ -208,22 +217,22 @@ namespace tut 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")], - std::string("Second Life Main Grid (Agni)")); + std::string("Second Life")); ensure_equals("Aditi has the right name and label", known_grids[std::string("util.aditi.lindenlab.com")], - std::string("Second Life Beta Test Grid (Aditi)")); + std::string("Second Life Beta")); ensure_equals("name for agni", LLGridManager::getInstance()->getGrid("util.agni.lindenlab.com"), std::string("util.agni.lindenlab.com")); ensure_equals("id for agni", 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://app.alchemyviewer.org/update"), - LLGridManager::getInstance()->getUpdateServiceURL()); + //ensure_equals("update url base for Agni", // relies on agni being the default + // std::string("https://update.secondlife.com/update"), + // LLGridManager::getInstance()->getUpdateServiceURL()); ensure_equals("label for agni", LLGridManager::getInstance()->getGridLabel("util.agni.lindenlab.com"), - std::string("Second Life Main Grid (Agni)")); + std::string("Second Life")); std::vector<std::string> login_uris; LLGridManager::getInstance()->getLoginURIs(std::string("util.agni.lindenlab.com"), login_uris); @@ -248,7 +257,7 @@ namespace tut std::string("Aditi")); ensure_equals("label for aditi", LLGridManager::getInstance()->getGridLabel("util.aditi.lindenlab.com"), - std::string("Second Life Beta Test Grid (Aditi)")); + std::string("Second Life Beta")); LLGridManager::getInstance()->getLoginURIs(std::string("util.aditi.lindenlab.com"), login_uris); @@ -282,22 +291,22 @@ namespace tut // 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")], - std::string("Second Life Main Grid (Agni)")); + std::string("Second Life")); ensure_equals("Aditi has the right name and label", known_grids[std::string("util.aditi.lindenlab.com")], - std::string("Second Life Beta Test Grid (Aditi)")); + std::string("Second Life Beta")); ensure_equals("name for agni", 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"), std::string("Agni")); - ensure_equals("update url base for Agni", // relies on agni being the default - std::string("https://app.alchemyviewer.org/update"), - LLGridManager::getInstance()->getUpdateServiceURL()); + //ensure_equals("update url base for Agni", // relies on agni being the default + // std::string("https://update.secondlife.com/update"), + // LLGridManager::getInstance()->getUpdateServiceURL()); ensure_equals("label for agni", LLGridManager::getInstance()->getGridLabel("util.agni.lindenlab.com"), - std::string("Second Life Main Grid (Agni)")); + std::string("Second Life")); std::vector<std::string> 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()); @@ -321,7 +330,7 @@ namespace tut std::string("Aditi")); ensure_equals("label for aditi", LLGridManager::getInstance()->getGridLabel("util.aditi.lindenlab.com"), - std::string("Second Life Beta Test Grid (Aditi)")); + std::string("Second Life Beta")); LLGridManager::getInstance()->getLoginURIs(std::string("util.aditi.lindenlab.com"), login_uris); ensure_equals("Number of login uris for aditi", 1, login_uris.size()); @@ -398,7 +407,7 @@ namespace tut // validate grid selection template<> template<> - void viewerNetworkTestObject::test<7>() + void viewerNetworkTestObject::test<3>() { // adding a grid with simply a name will populate the values. llofstream gridfile(TEST_FILENAME); @@ -407,10 +416,10 @@ namespace tut LLGridManager::getInstance()->initialize(TEST_FILENAME); - LLGridManager::getInstance()->setGridChoice("util.agni.lindenlab.com"); + LLGridManager::getInstance()->setGridChoice(std::string("util.agni.lindenlab.com")); ensure_equals("getGridLabel", LLGridManager::getInstance()->getGridLabel(), - std::string("Second Life Main Grid (Agni)")); + std::string("Second Life")); ensure_equals("getGridId", LLGridManager::getInstance()->getGridId(), std::string("Agni")); @@ -423,10 +432,11 @@ namespace tut ensure_equals("getLoginPage", LLGridManager::getInstance()->getLoginPage(), std::string("https://viewer-splash.secondlife.com/")); - ensure_equals("update url base for Agni", // relies on agni being the default - std::string("https://app.alchemyviewer.org/update"), - LLGridManager::getInstance()->getUpdateServiceURL()); - ensure("Is Agni a production grid", LLGridManager::getInstance()->isInProductionGrid()); + //ensure_equals("update url base for Agni", // relies on agni being the default + // std::string("https://update.secondlife.com/update"), + // LLGridManager::getInstance()->getUpdateServiceURL()); + ensure("Is Agni Second Life", LLGridManager::getInstance()->isInSecondlife()); + ensure("Agni is NOT OpenSim", !LLGridManager::getInstance()->isInOpenSim()); std::vector<std::string> uris; LLGridManager::getInstance()->getLoginURIs(uris); ensure_equals("getLoginURIs size", 1, uris.size()); @@ -434,7 +444,7 @@ namespace tut uris[0], std::string("https://login.agni.lindenlab.com/cgi-bin/login.cgi")); - LLGridManager::getInstance()->setGridChoice("altgrid.long.name"); + LLGridManager::getInstance()->setGridChoice(std::string("altgrid.long.name")); ensure_equals("getGridLabel", LLGridManager::getInstance()->getGridLabel(), std::string("Alternative Grid")); @@ -443,8 +453,10 @@ namespace tut 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()); + ensure("alternative grid is OpenSim", + LLGridManager::getInstance()->isInOpenSim()); + ensure("alternative grid is not Second Life", + !LLGridManager::getInstance()->isInSecondlife()); } } diff --git a/indra/newview/tests/llworldmap_test.cpp b/indra/newview/tests/llworldmap_test.cpp index 16474ff8ff7..d058ca76d04 100644 --- a/indra/newview/tests/llworldmap_test.cpp +++ b/indra/newview/tests/llworldmap_test.cpp @@ -51,6 +51,22 @@ void LLGLTexture::setBoostLevel(S32 ) { } void LLGLTexture::setAddressMode(LLTexUnit::eTextureAddressMode ) { } LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTexture(const LLUUID&, FTType, BOOL, LLGLTexture::EBoostLevel, S8, LLGLint, LLGLenum, LLHost ) { return NULL; } +LLViewerFetchedTexture* LLViewerTextureManager::findFetchedTexture(const LLUUID& id, S32 tex_type) { return NULL; } + +class LLTextureCache +{ +public: + bool removeFromCache(const LLUUID& id); +}; + +bool LLTextureCache::removeFromCache(const LLUUID& id) { return true; } + +class LLAppViewer +{ + static LLTextureCache* sTextureCache; +}; + +LLTextureCache* LLAppViewer::sTextureCache = NULL; // Stub related map calls LLWorldMapMessage::LLWorldMapMessage() { } @@ -395,8 +411,8 @@ namespace tut bool success; LLUUID id; std::string name_sim = SIM_NAME_TEST; - success = mWorld->insertRegion( U32(X_WORLD_TEST), - U32(Y_WORLD_TEST), + success = mWorld->insertRegion(U32(X_WORLD_TEST), U32(Y_WORLD_TEST), + REGION_WIDTH_UNITS, REGION_WIDTH_UNITS, name_sim, id, SIM_ACCESS_PG, diff --git a/indra/newview/tests/llworldmipmap_test.cpp b/indra/newview/tests/llworldmipmap_test.cpp index cf6cd61262e..10c3d1ca21f 100644 --- a/indra/newview/tests/llworldmipmap_test.cpp +++ b/indra/newview/tests/llworldmipmap_test.cpp @@ -42,6 +42,17 @@ // * Do not make any assumption as to how those classes or methods work (i.e. don't copy/paste code) // * A simulator for a class can be implemented here. Please comment and document thoroughly. + +#include "../llagent.h" +LLAgent::LLAgent() : mAgentAccess(NULL) { } +LLAgent::~LLAgent() { } +LLViewerRegion* LLAgent::getRegion() const { return nullptr; } + +LLAgent gAgent; + +#include "../llviewerregion.h" +std::string LLViewerRegion::getMapServerURL() const { return {}; } + void LLGLTexture::setBoostLevel(S32 ) { } LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromUrl(const std::string&, FTType, BOOL, LLGLTexture::EBoostLevel, S8, LLGLint, LLGLenum, const LLUUID& ) { return NULL; } -- GitLab