Daniel Carvalho has uploaded this change for review. ( https://gem5-review.googlesource.com/c/public/gem5/+/41398 )

Change subject: base: Add unit test for base/logging
......................................................................

base: Add unit test for base/logging

Add unit test for base/logging.

One of the tests has been disabled due to incorrectness
of output.

Change-Id: Ia890c06b44134b70eada7a9deadef882f00a5c27
Signed-off-by: Daniel R. Carvalho <[email protected]>
---
M src/base/SConscript
A src/base/logging.test.cc
2 files changed, 588 insertions(+), 0 deletions(-)



diff --git a/src/base/SConscript b/src/base/SConscript
index 03f54f6..7fe77ae 100644
--- a/src/base/SConscript
+++ b/src/base/SConscript
@@ -58,6 +58,7 @@
 GTest('inifile.test', 'inifile.test.cc', 'inifile.cc', 'str.cc')
 GTest('intmath.test', 'intmath.test.cc', with_tag('gem5 logging'))
 Source('logging.cc', add_tags='gem5 logging')
+GTest('logging.test', 'logging.test.cc', with_tag('gem5 logging'))
 Source('match.cc')
 GTest('match.test', 'match.test.cc', 'match.cc', 'str.cc')
 Source('output.cc')
diff --git a/src/base/logging.test.cc b/src/base/logging.test.cc
new file mode 100644
index 0000000..3075ab7
--- /dev/null
+++ b/src/base/logging.test.cc
@@ -0,0 +1,587 @@
+/*
+ * Copyright (c) 2021 Daniel R. Carvalho
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "base/logging.hh"
+
+class TestLogger : public Logger
+{
+  public:
+    using Logger::Logger;
+
+  protected:
+    void log(const Loc &loc, std::string s) override { std::cerr << s; }
+};
+
+#if TRACING_ON
+/** Test that a logger cannot be created with an empty prefix. */
+TEST(LoggingDeathTest, EmptyPrefix)
+{
+    ASSERT_DEATH(TestLogger(nullptr), "");
+}
+#endif
+
+/** Test the most basic print using TestLogger. */
+TEST(LoggingTest, TestLoggerBasicPrint)
+{
+    TestLogger logger("test: ");
+
+    // The logger will automatically add '\n' to the end of the message
+    // if it does not already have at least one.
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), "message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: message\n");
+
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), "message\n");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: message\n");
+
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), "sample message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: sample message\n");
+
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10),
+        "sample message\nwith \nthree lines");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: sample message\nwith \nthree lines\n");
+
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), "sample message\n\n");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: sample message\n\n");
+}
+
+/** Test the variadic-arg print for chars using TestLogger. */
+TEST(LoggingTest, TestLoggerVariadicCharPrint)
+{
+    TestLogger logger("test: ");
+
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), (const char *)"%s message",
+        "sample");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: sample message\n");
+
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), (const char *)"sample %s %d\n",
+        "message", 2);
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: sample message 2\n");
+}
+
+/** Test the variadic-arg print for strings using TestLogger. */
+TEST(LoggingTest, TestLoggerVariadicStringPrint)
+{
+    TestLogger logger("test: ");
+
+    testing::internal::CaptureStderr();
+ logger.print(Logger::Loc("File", 10), std::string("%s message"), "sample");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: sample message\n");
+
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), std::string("sample %s %d\n"),
+        "message", 2);
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: sample message 2\n");
+}
+
+/**
+ * Test the variadic-arg print for chars with arguments missing using
+ * TestLogger.
+ */
+TEST(LoggingTest, TestLoggerVariadicCharMissingPrint)
+{
+    TestLogger logger("test: ");
+
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), (const char *)"%s message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: <extra arg>% message\n");
+
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), (const char *)"%s mes%ca%cge",
+        "sample", 's');
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: sample messa<extra arg>%ge\n");
+}
+
+/**
+ * Test the variadic-arg print for strings with arguments missing using
+ * TestLogger.
+ */
+TEST(LoggingTest, DISABLED_TestLoggerVariadicStringMissingPrint)
+{
+    TestLogger logger("test: ");
+
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), std::string("%s message"));
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: <extra arg>% message\n");
+
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), std::string("%s mes%ca%cge"),
+        "sample", 's');
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: sample messa<extra arg>%ge\n");
+}
+
+/** Test that no message is shown when printing with a disabled logger. */
+TEST(LoggingTest, TestLoggerDisabledPrint)
+{
+    class DisabledTestLogger : public TestLogger
+    {
+      public:
+        DisabledTestLogger(const char *prefix)
+          : TestLogger(prefix)
+        {
+            enabled = false;
+        }
+    } logger("test: ");
+
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), "message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(), "");
+}
+
+/** Test printing with the warn logger, enabled and disabled. */
+TEST(LoggingTest, WarnLoggerPrint)
+{
+    Logger::setLevel(Logger::WARN);
+    Logger &logger = Logger::getWarn();
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), "message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(), "warn: message\n");
+
+    // PANIC does not include WARN
+    Logger::setLevel(Logger::PANIC);
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), "message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(), "");
+
+    Logger::setLevel(Logger::NUM_LOG_LEVELS);
+}
+
+/** Test printing with the info logger, enabled and disabled. */
+TEST(LoggingTest, InfoLoggerPrint)
+{
+    Logger::setLevel(Logger::INFO);
+    Logger &logger = Logger::getInfo();
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), "message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(), "info: message\n");
+
+    // PANIC does not include INFO
+    Logger::setLevel(Logger::PANIC);
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), "message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(), "");
+
+    Logger::setLevel(Logger::NUM_LOG_LEVELS);
+}
+
+/** Test printing with the hack logger, enabled and disabled. */
+TEST(LoggingTest, HackLoggerPrint)
+{
+    Logger::setLevel(Logger::HACK);
+    Logger &logger = Logger::getHack();
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), "message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(), "hack: message\n");
+
+    // PANIC does not include HACK
+    Logger::setLevel(Logger::PANIC);
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), "message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(), "");
+
+    Logger::setLevel(Logger::NUM_LOG_LEVELS);
+}
+
+/** Test printing with the fatal logger, enabled and disabled. */
+TEST(LoggingTest, FatalLoggerPrint)
+{
+    Logger &logger = Logger::getFatal();
+
+    // The actual value of memory usage is not relevant
+    Logger::setLevel(Logger::FATAL);
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), "message");
+    ASSERT_NE(testing::internal::GetCapturedStderr().find(
+        "fatal: message\nMemory Usage:"), std::string::npos);
+
+    // PANIC does not include FATAL
+    Logger::setLevel(Logger::PANIC);
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), "message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(), "");
+
+    Logger::setLevel(Logger::NUM_LOG_LEVELS);
+}
+
+/** Test printing with the panic logger, which cannot be disabled. */
+TEST(LoggingTest, PanicLoggerPrint)
+{
+    // The actual value of memory usage is not relevant
+    Logger::setLevel(Logger::PANIC);
+    Logger &logger = Logger::getPanic();
+    testing::internal::CaptureStderr();
+    logger.print(Logger::Loc("File", 10), "message");
+    std::string error = testing::internal::GetCapturedStderr().substr();
+    ASSERT_NE(error.find("panic: message\nMemory Usage:"),
+        std::string::npos);
+
+    Logger::setLevel(Logger::NUM_LOG_LEVELS);
+}
+
+/** Test that the test logger's exit helper will end execution gracefully. */
+TEST(LoggingDeathTest, TestLoggerExitHelper)
+{
+    ASSERT_DEATH(TestLogger("test: ").exit_helper(), "");
+}
+
+/** Test that the warn logger's exit helper will end execution gracefully. */
+TEST(LoggingDeathTest, WarnLoggerExitHelper)
+{
+    ASSERT_DEATH(Logger::getWarn().exit_helper(), "");
+}
+
+/** Test that the info logger's exit helper will end execution gracefully. */
+TEST(LoggingDeathTest, InfoLoggerExitHelper)
+{
+    ASSERT_DEATH(Logger::getInfo().exit_helper(), "");
+}
+
+/** Test that the hack logger's exit helper will end execution gracefully. */
+TEST(LoggingDeathTest, HackLoggerExitHelper)
+{
+    ASSERT_DEATH(Logger::getHack().exit_helper(), "");
+}
+
+/** Test that the fatal logger's exit helper will end execution with error. */
+TEST(LoggingDeathTest, FatalLoggerExitHelper)
+{
+    ASSERT_DEATH(Logger::getFatal().exit_helper(), "");
+}
+
+/** Test that the panic logger's exit helper will end execution with error. */
+TEST(LoggingDeathTest, PanicLoggerExitHelper)
+{
+    ASSERT_DEATH(Logger::getPanic().exit_helper(), "");
+}
+
+/** Test macro base_message. */
+TEST(LoggingTest, BaseMessage)
+{
+    TestLogger logger("test: ");
+
+    // The logger will automatically add '\n' to the end of the message
+    // if it does not already have at least one.
+    testing::internal::CaptureStderr();
+    base_message(logger, "message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: message\n");
+
+    testing::internal::CaptureStderr();
+    base_message(logger, "message\n");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: message\n");
+
+    testing::internal::CaptureStderr();
+    base_message(logger, "sample message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: sample message\n");
+
+    testing::internal::CaptureStderr();
+    base_message(logger, "sample message\nwith \nthree lines");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: sample message\nwith \nthree lines\n");
+
+    testing::internal::CaptureStderr();
+    base_message(logger, "sample message\n\n");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: sample message\n\n");
+
+    testing::internal::CaptureStderr();
+    base_message(logger, "%s message", "sample");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: sample message\n");
+
+    testing::internal::CaptureStderr();
+    base_message(logger, "sample %s %d\n", "message", 2);
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "test: sample message 2\n");
+}
+
+/** Test that base_message_once only prints the message once in a loop. */
+TEST(LoggingTest, BaseMessageOnce)
+{
+    TestLogger logger("test: ");
+
+    for (int i = 0; i < 10; i++) {
+        testing::internal::CaptureStderr();
+        base_message_once(logger, "message\n");
+        ASSERT_EQ(testing::internal::GetCapturedStderr(),
+            (i == 0) ? "test: message\n" : "");
+    }
+}
+
+/** Test that exit_message prints a message and exits. */
+TEST(LoggingDeathTest, ExitMessage)
+{
+    TestLogger logger("test: ");
+    ASSERT_DEATH(exit_message(logger, "message\n"), "test: message\n");
+}
+
+/** Test macro panic. */
+TEST(LoggingDeathTest, Panic)
+{
+    ASSERT_DEATH(panic("message\n"),
+        ::testing::StartsWith("panic: message\nMemory Usage:"));
+}
+
+/** Test macro fatal. */
+TEST(LoggingDeathTest, Fatal)
+{
+    ASSERT_DEATH(fatal("message\n"),
+        ::testing::StartsWith("fatal: message\nMemory Usage:"));
+}
+
+/** Test that panic_if only prints the message when the condition is true. */
+TEST(LoggingDeathTest, PanicIf)
+{
+    for (int i = 0; i < 4; i++) {
+        panic_if(i == 4, "message\n");
+    }
+    ASSERT_DEATH(panic_if(true, "message\n"), ::testing::StartsWith(
+        "panic: panic condition true occurred: message\nMemory Usage:"));
+}
+
+/** Test that fatal_if only prints the message when the condition is true. */
+TEST(LoggingDeathTest, FatalIf)
+{
+    for (int i = 0; i < 4; i++) {
+        fatal_if(i == 4, "message\n");
+    }
+    ASSERT_DEATH(fatal_if(true, "message\n"), ::testing::StartsWith(
+        "fatal: fatal condition true occurred: message\nMemory Usage:"));
+}
+
+/** Test macro warn. */
+TEST(LoggingTest, Warn)
+{
+    // The logger will automatically add '\n' to the end of the message
+    // if it does not already have at least one.
+    testing::internal::CaptureStderr();
+    warn("message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "warn: message\n");
+
+    testing::internal::CaptureStderr();
+    warn("message\n");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "warn: message\n");
+
+    testing::internal::CaptureStderr();
+    warn("sample message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "warn: sample message\n");
+
+    testing::internal::CaptureStderr();
+    warn("sample message\nwith \nthree lines");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "warn: sample message\nwith \nthree lines\n");
+
+    testing::internal::CaptureStderr();
+    warn("sample message\n\n");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "warn: sample message\n\n");
+
+    testing::internal::CaptureStderr();
+    warn("%s message", "sample");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "warn: sample message\n");
+
+    testing::internal::CaptureStderr();
+    warn("sample %s %d\n", "message", 2);
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "warn: sample message 2\n");
+}
+
+/** Test macro inform. */
+TEST(LoggingTest, Inform)
+{
+    // The logger will automatically add '\n' to the end of the message
+    // if it does not already have at least one.
+    testing::internal::CaptureStderr();
+    inform("message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "info: message\n");
+
+    testing::internal::CaptureStderr();
+    inform("message\n");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "info: message\n");
+
+    testing::internal::CaptureStderr();
+    inform("sample message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "info: sample message\n");
+
+    testing::internal::CaptureStderr();
+    inform("sample message\nwith \nthree lines");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "info: sample message\nwith \nthree lines\n");
+
+    testing::internal::CaptureStderr();
+    inform("sample message\n\n");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "info: sample message\n\n");
+
+    testing::internal::CaptureStderr();
+    inform("%s message", "sample");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "info: sample message\n");
+
+    testing::internal::CaptureStderr();
+    inform("sample %s %d\n", "message", 2);
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "info: sample message 2\n");
+}
+
+/** Test macro hack. */
+TEST(LoggingTest, Hack)
+{
+    // The logger will automatically add '\n' to the end of the message
+    // if it does not already have at least one.
+    testing::internal::CaptureStderr();
+    hack("message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "hack: message\n");
+
+    testing::internal::CaptureStderr();
+    hack("message\n");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "hack: message\n");
+
+    testing::internal::CaptureStderr();
+    hack("sample message");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "hack: sample message\n");
+
+    testing::internal::CaptureStderr();
+    hack("sample message\nwith \nthree lines");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "hack: sample message\nwith \nthree lines\n");
+
+    testing::internal::CaptureStderr();
+    hack("sample message\n\n");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "hack: sample message\n\n");
+
+    testing::internal::CaptureStderr();
+    hack("%s message", "sample");
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "hack: sample message\n");
+
+    testing::internal::CaptureStderr();
+    hack("sample %s %d\n", "message", 2);
+    ASSERT_EQ(testing::internal::GetCapturedStderr(),
+        "hack: sample message 2\n");
+}
+
+/** Test that warn_once only prints the message once in a loop. */
+TEST(LoggingTest, WarnOnce)
+{
+    for (int i = 0; i < 10; i++) {
+        testing::internal::CaptureStderr();
+        warn_once("message\n");
+        ASSERT_EQ(testing::internal::GetCapturedStderr(),
+            (i == 0) ? "warn: message\n" : "");
+    }
+}
+
+/** Test that inform_once only prints the message once in a loop. */
+TEST(LoggingTest, InformOnce)
+{
+    for (int i = 0; i < 10; i++) {
+        testing::internal::CaptureStderr();
+        inform_once("message\n");
+        ASSERT_EQ(testing::internal::GetCapturedStderr(),
+            (i == 0) ? "info: message\n" : "");
+    }
+}
+
+/** Test that hack_once only prints the message once in a loop. */
+TEST(LoggingTest, HackOnce)
+{
+    for (int i = 0; i < 10; i++) {
+        testing::internal::CaptureStderr();
+        hack_once("message\n");
+        ASSERT_EQ(testing::internal::GetCapturedStderr(),
+            (i == 0) ? "hack: message\n" : "");
+    }
+}
+
+/** Test that warn_if only prints the message when the condition is true. */
+TEST(LoggingTest, WarnIf)
+{
+    for (int i = 0; i < 10; i++) {
+        testing::internal::CaptureStderr();
+        warn_if(i % 4, "message\n");
+        ASSERT_EQ(testing::internal::GetCapturedStderr(),
+            (i % 4) ? "warn: message\n" : "");
+    }
+}
+
+/** Test that warn_if_once only prints the message once in a loop. */
+TEST(LoggingTest, WarnIfOnce)
+{
+    for (int i = 0; i < 10; i++) {
+        testing::internal::CaptureStderr();
+        warn_if_once(i == 3, "message\n");
+        ASSERT_EQ(testing::internal::GetCapturedStderr(),
+            (i == 3) ? "warn: message\n" : "");
+    }
+}
+
+#ifndef NDEBUG
+/** Test macro chatty_assert. */
+TEST(LoggingDeathTest, ChattyAssert)
+{
+    chatty_assert(true, "message\n");
+    ASSERT_DEATH(chatty_assert(false, "message\n"), ::testing::StartsWith(
+        "panic: assert(false) failed: message\nMemory Usage:"));
+}
+#endif

--
To view, visit https://gem5-review.googlesource.com/c/public/gem5/+/41398
To unsubscribe, or for help writing mail filters, visit https://gem5-review.googlesource.com/settings

Gerrit-Project: public/gem5
Gerrit-Branch: develop
Gerrit-Change-Id: Ia890c06b44134b70eada7a9deadef882f00a5c27
Gerrit-Change-Number: 41398
Gerrit-PatchSet: 1
Gerrit-Owner: Daniel Carvalho <[email protected]>
Gerrit-MessageType: newchange
_______________________________________________
gem5-dev mailing list -- [email protected]
To unsubscribe send an email to [email protected]
%(web_page_url)slistinfo%(cgiext)s/%(_internal_name)s

Reply via email to