From 8ed755cfb0779c3e1119d17a7877768af1a01682 Mon Sep 17 00:00:00 2001 From: Michael Stahl Date: Wed, 31 Jul 2024 14:42:57 +0200 Subject: [PATCH] pyuno,unotest,xmlsecurity: copy GPG test files for UITtest The problem was that running UITest_xmlsecurity_gpg changed the files in test/signing-keys/ ... prevent that by copying the directory in that test, which is more complicated than initially expected. Change-Id: Ie3be922e0b2e9dae49f9a70e35ad5270c90b4fc4 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/171322 Reviewed-by: Thorsten Behrens Reviewed-by: Michael Stahl Tested-by: Jenkins --- include/unotest/macros_test.hxx | 7 +- pyuno/source/module/pyuno_impl.hxx | 1 + pyuno/source/module/pyuno_module.cxx | 61 ++++++++++++++++ solenv/gbuild/UITest.mk | 7 +- uitest/libreoffice/connection.py | 2 +- unotest/source/cpp/macros_test.cxx | 93 +++++++++++++++++++----- xmlsecurity/UITest_xmlsecurity_gpg.mk | 3 + xmlsecurity/qa/uitest/gpg/encrypt.py | 20 +++++ xmlsecurity/qa/unit/signing/signing.cxx | 2 +- xmlsecurity/qa/unit/signing/signing2.cxx | 2 +- 10 files changed, 168 insertions(+), 30 deletions(-) diff --git a/include/unotest/macros_test.hxx b/include/unotest/macros_test.hxx index cf667125e8f0..663fcf22b68d 100644 --- a/include/unotest/macros_test.hxx +++ b/include/unotest/macros_test.hxx @@ -98,8 +98,8 @@ public: // note: there is no tearDownX509 void setUpX509(const test::Directories& rDirectories, const OUString& rTestName); - void setUpGpg(const test::Directories& rDirectories, const OUString& rTestName); - void tearDownGpg(); + static void setUpGpg(const test::Directories& rDirectories, std::u16string_view rTestName); + static void tearDownGpg(); static bool IsValid(const css::uno::Reference& cert, const css::uno::Reference& env); @@ -113,9 +113,6 @@ protected: private: std::unique_ptr mpDll; -#if HAVE_GPGCONF_SOCKETDIR - OString m_gpgconfCommandPrefix; -#endif }; } diff --git a/pyuno/source/module/pyuno_impl.hxx b/pyuno/source/module/pyuno_impl.hxx index 946991c3864b..878367d3764a 100644 --- a/pyuno/source/module/pyuno_impl.hxx +++ b/pyuno/source/module/pyuno_impl.hxx @@ -222,6 +222,7 @@ struct RuntimeCargo css::uno::Reference< css::beans::XIntrospection > xIntrospection; PyRef dictUnoModule; osl::Module testModule; + osl::Module unoTestModule; bool valid; ExceptionClassMap exceptionMap; ClassSet interfaceSet; diff --git a/pyuno/source/module/pyuno_module.cxx b/pyuno/source/module/pyuno_module.cxx index b3cdc07b4703..1fc2e8bf00a2 100644 --- a/pyuno/source/module/pyuno_module.cxx +++ b/pyuno/source/module/pyuno_module.cxx @@ -383,6 +383,65 @@ static PyObject* deinitTestEnvironment( return Py_None; } +static PyObject* initTestEnvironmentGPG( + SAL_UNUSED_PARAMETER PyObject*, PyObject* args) +{ + // this tries to set up certificate stores for unit tests + // which is only possible indirectly because pyuno is URE + // so load "unotest" library and invoke a function there to do the work + Runtime const runtime; + osl::Module & rModule(runtime.getImpl()->cargo->unoTestModule); + assert(!rModule.is()); + try + { + char *const testlib = getenv("UNOTEST_LIB"); + if (!testlib) { abort(); } +#ifdef _WIN32 + OString const libname = OString(testlib, strlen(testlib)) + .replaceAll(OString('/'), OString('\\')); +#else + OString const libname(testlib, strlen(testlib)); +#endif + + rModule.load(OStringToOUString(libname, osl_getThreadTextEncoding()), + SAL_LOADMODULE_LAZY | SAL_LOADMODULE_GLOBAL); + if (!rModule.is()) { abort(); } + oslGenericFunction const pFunc(rModule.getFunctionSymbol("test_init_gpg")); + if (!pFunc) { abort(); } + char * pTestDirURL; + if (!PyArg_ParseTuple(args, "s", &pTestDirURL)) { abort(); } + OUString const testDirURL(OUString::createFromAscii(pTestDirURL)); + reinterpret_cast(pFunc)(testDirURL); + } + catch (const css::uno::Exception &) + { + abort(); + } + return Py_None; +} + +static PyObject* deinitTestEnvironmentGPG( + SAL_UNUSED_PARAMETER PyObject*, SAL_UNUSED_PARAMETER PyObject*) +{ + Runtime const runtime; + osl::Module & rModule(runtime.getImpl()->cargo->unoTestModule); + if (rModule.is()) + { + try + { + oslGenericFunction const pFunc( + rModule.getFunctionSymbol("test_deinit_gpg")); + if (!pFunc) { abort(); } + reinterpret_cast(pFunc)(); + } + catch (const css::uno::Exception &) + { + abort(); + } + } + return Py_None; +} + PyObject * extractOneStringArg( PyObject *args, char const *funcName ) { if( !PyTuple_Check( args ) || PyTuple_Size( args) != 1 ) @@ -852,6 +911,8 @@ struct PyMethodDef PyUNOModule_methods [] = { {"private_initTestEnvironment", initTestEnvironment, METH_VARARGS, nullptr}, {"private_deinitTestEnvironment", deinitTestEnvironment, METH_VARARGS, nullptr}, + {"private_initTestEnvironmentGPG", initTestEnvironmentGPG, METH_VARARGS, nullptr}, + {"private_deinitTestEnvironmentGPG", deinitTestEnvironmentGPG, METH_VARARGS, nullptr}, {"getComponentContext", getComponentContext, METH_VARARGS, nullptr}, #if defined __clang__ #pragma clang diagnostic push diff --git a/solenv/gbuild/UITest.mk b/solenv/gbuild/UITest.mk index 300c28b350ea..282df21b9112 100644 --- a/solenv/gbuild/UITest.mk +++ b/solenv/gbuild/UITest.mk @@ -41,9 +41,6 @@ gb_UITest_COMMAND = $(ICECREAM_RUN) $(gb_CppunitTest_coredumpctl_run) $(gb_Cppun gb_TEST_ENV_VARS += LIBO_LANG=C -# GNUPGHOME is needed for tests using the LibreOffice GPG features. -gb_TEST_ENV_VARS += GNUPGHOME="$(SRCDIR)/test/signing-keys" - .PHONY : $(call gb_UITest_get_clean_target,%) $(call gb_UITest_get_clean_target,%) : $(call gb_Helper_abbreviate_dirs,\ @@ -51,6 +48,9 @@ $(call gb_UITest_get_clean_target,%) : ifneq ($(DISABLE_PYTHON),TRUE) +# dlopening unotest requires this +gb_UITest_PRECOMMAND = $(gb_PythonTest_PRECOMMAND) + # qadevOOo/qa/registrymodifications.xcu is copied to user profile directory to ensure en_US locale; # this might be overwritten later when gb_UITest_use_config is set .PHONY : $(call gb_UITest_get_target,%) @@ -73,6 +73,7 @@ else $(DEFS) \ $(if $(filter WNT,$(OS)),SAL_LOG_FILE="$(dir $(call gb_UITest_get_target,$*))/soffice.out.log") \ TEST_LIB=$(call gb_Library_get_target,test) \ + UNOTEST_LIB=$(call gb_Library_get_target,unotest) \ URE_BOOTSTRAP=vnd.sun.star.pathname:$(call gb_Helper_get_rcfile,$(INSTROOT)/$(LIBO_ETC_FOLDER)/fundamental) \ PYTHONPATH="$(PYPATH)" \ TestUserDir="$(call gb_Helper_make_url,$(dir $(call gb_UITest_get_target,$*)))" \ diff --git a/uitest/libreoffice/connection.py b/uitest/libreoffice/connection.py index 4f901130f223..1d1730d98d3e 100644 --- a/uitest/libreoffice/connection.py +++ b/uitest/libreoffice/connection.py @@ -24,7 +24,7 @@ except ImportError: def signal_handler(signal_num, frame): signal_name = signal.Signals(signal_num).name - print(f'Signal handler called with signal {signal_name} ({signal_num})', flush=True) + #print(f'Signal handler called with signal {signal_name} ({signal_num})', flush=True) class OfficeConnection: def __init__(self, args): diff --git a/unotest/source/cpp/macros_test.cxx b/unotest/source/cpp/macros_test.cxx index 3cf6a406735e..245ef13e5e8d 100644 --- a/unotest/source/cpp/macros_test.cxx +++ b/unotest/source/cpp/macros_test.cxx @@ -145,27 +145,61 @@ void MacrosTest::setUpX509(const test::Directories& rDirectories, const OUString #endif } -void MacrosTest::setUpGpg(const test::Directories& rDirectories, const OUString& rTestName) +#if HAVE_GPGCONF_SOCKETDIR +// mutable global should be tolerable in test lib +static OString g_gpgconfCommandPrefix; +#endif + +extern "C" { + +SAL_DLLPUBLIC_EXPORT +void test_init_gpg(OUString const& rTargetDir) { - OUString aSourceDir = rDirectories.getURLFromSrc(u"/test/signing-keys/"); - OUString aTargetDir - = rDirectories.getURLFromWorkdir(Concat2View("CppunitTest/" + rTestName + ".test.user")); + const char* pSrcRoot = getenv("SRC_ROOT"); + if (!pSrcRoot) + { + abort(); + } + OUString const srcRootPath(OUString(pSrcRoot, strlen(pSrcRoot), osl_getThreadTextEncoding())); + OUString const sourcePath(srcRootPath + "/test/signing-keys/"); + OUString aSourceDir; + osl::FileBase::RC e = osl::FileBase::getFileURLFromSystemPath(sourcePath, aSourceDir); + if (osl::FileBase::E_None != e) + { + abort(); + } OUString aTargetPath; - osl::FileBase::getSystemPathFromFileURL(aTargetDir, aTargetPath); + osl::FileBase::getSystemPathFromFileURL(rTargetDir, aTargetPath); + + auto const rc = osl::Directory::create(rTargetDir); + if (osl::FileBase::E_None != rc && osl::FileBase::E_EXIST != rc) + { + SAL_WARN("test", "creating target dir failed, aborting"); + abort(); + } // Make gpg use our own defined setup & keys - osl::File::copy(aSourceDir + "pubring.gpg", aTargetDir + "/pubring.gpg"); - osl::File::copy(aSourceDir + "random_seed", aTargetDir + "/random_seed"); - osl::File::copy(aSourceDir + "secring.gpg", aTargetDir + "/secring.gpg"); - osl::File::copy(aSourceDir + "trustdb.gpg", aTargetDir + "/trustdb.gpg"); + if (osl::FileBase::E_None + != osl::File::copy(aSourceDir + "pubring.gpg", rTargetDir + "/pubring.gpg") + || osl::FileBase::E_None + != osl::File::copy(aSourceDir + "random_seed", rTargetDir + "/random_seed") + || osl::FileBase::E_None + != osl::File::copy(aSourceDir + "secring.gpg", rTargetDir + "/secring.gpg") + || osl::FileBase::E_None + != osl::File::copy(aSourceDir + "trustdb.gpg", rTargetDir + "/trustdb.gpg")) + { + SAL_WARN("test", "copying files failed, aborting"); + abort(); + } + // note: this doesn't work for UITest because "os.environ" is a copy :( OUString gpgHomeVar(u"GNUPGHOME"_ustr); osl_setEnvironment(gpgHomeVar.pData, aTargetPath.pData); #if HAVE_GPGCONF_SOCKETDIR auto const ldPath = std::getenv("LIBO_LD_PATH"); - m_gpgconfCommandPrefix + g_gpgconfCommandPrefix = ldPath == nullptr ? OString() : OString::Concat("LD_LIBRARY_PATH=") + ldPath + " "; OString path; bool ok = aTargetPath.convertToString(&path, osl_getThreadTextEncoding(), @@ -173,32 +207,53 @@ void MacrosTest::setUpGpg(const test::Directories& rDirectories, const OUString& | RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR); // if conversion fails, at least provide a best-effort conversion in the message here, for // context - CPPUNIT_ASSERT_MESSAGE(OUStringToOString(aTargetPath, RTL_TEXTENCODING_UTF8).getStr(), ok); - m_gpgconfCommandPrefix += "GNUPGHOME=" + path + " " GPGME_GPGCONF; + if (!ok) + { + SAL_WARN("test", "converting path failed, aborting: " << aTargetPath); + abort(); + } + g_gpgconfCommandPrefix += "GNUPGHOME=" + path + " " GPGME_GPGCONF; // HAVE_GPGCONF_SOCKETDIR is only defined in configure.ac for Linux for now, so (a) std::system // behavior will conform to POSIX (and the relevant env var to set is named LD_LIBRARY_PATH), and // (b) gpgconf --create-socketdir should return zero: - OString cmd = m_gpgconfCommandPrefix + " --create-socketdir"; + OString cmd = g_gpgconfCommandPrefix + " --create-socketdir"; int res = std::system(cmd.getStr()); - CPPUNIT_ASSERT_EQUAL_MESSAGE(cmd.getStr(), 0, res); + if (res != 0) + { + SAL_WARN("test", "invoking gpgconf failed, aborting: " << cmd); + abort(); + } #else - (void)this; + (void)rTargetDir; #endif } -void MacrosTest::tearDownGpg() +SAL_DLLPUBLIC_EXPORT void test_deinit_gpg() { #if HAVE_GPGCONF_SOCKETDIR // HAVE_GPGCONF_SOCKETDIR is only defined in configure.ac for Linux for now, so (a) std::system // behavior will conform to POSIX, and (b) gpgconf --remove-socketdir should return zero: - OString cmd = m_gpgconfCommandPrefix + " --remove-socketdir"; + CPPUNIT_ASSERT(!g_gpgconfCommandPrefix.isEmpty()); + OString cmd = g_gpgconfCommandPrefix + " --remove-socketdir"; int res = std::system(cmd.getStr()); CPPUNIT_ASSERT_EQUAL_MESSAGE(cmd.getStr(), 0, res); -#else - (void)this; + g_gpgconfCommandPrefix.clear(); #endif } +} // extern "C" + +void MacrosTest::setUpGpg(const test::Directories& rDirectories, + std::u16string_view const rTestName) +{ + OUString aTargetDir = rDirectories.getURLFromWorkdir( + Concat2View("CppunitTest/" + OUString(rTestName.data(), rTestName.size()) + ".test.user")); + + return test_init_gpg(aTargetDir); +} + +void MacrosTest::tearDownGpg() { return test_deinit_gpg(); } + namespace { struct Valid diff --git a/xmlsecurity/UITest_xmlsecurity_gpg.mk b/xmlsecurity/UITest_xmlsecurity_gpg.mk index 90cb75bee813..4603ca7ede6a 100644 --- a/xmlsecurity/UITest_xmlsecurity_gpg.mk +++ b/xmlsecurity/UITest_xmlsecurity_gpg.mk @@ -17,4 +17,7 @@ $(eval $(call gb_UITest_set_defs,xmlsecurity_gpg, \ TDOC="$(SRCDIR)/xmlsecurity/qa/uitest/data" \ )) +# oneprocess prevents setting GNUPGHOME +$(eval $(call gb_UITest_avoid_oneprocess,xmlsecurity_gpg)) + # vim: set noet sw=4 ts=4: diff --git a/xmlsecurity/qa/uitest/gpg/encrypt.py b/xmlsecurity/qa/uitest/gpg/encrypt.py index ca2fa5f87f9c..70c9c0e9ffc1 100644 --- a/xmlsecurity/qa/uitest/gpg/encrypt.py +++ b/xmlsecurity/qa/uitest/gpg/encrypt.py @@ -9,8 +9,11 @@ from uitest.framework import UITestCase from libreoffice.uno.propertyvalue import mkPropertyValues +import pyuno from tempfile import TemporaryDirectory +from urllib.parse import urljoin, urlparse +from urllib.request import url2pathname import os.path @@ -19,6 +22,23 @@ import os.path # Requires the environment variable GNUPGHOME to be set correctly. # See solenv/gbuild/UITest.mk class GpgEncryptTest(UITestCase): + + # should this be setUp() or setUpClass()? + # as setUp(), any test's change to the files should be overwritten before + # the next test, which could be an advantage. + def setUp(self): + testdir = os.getenv("TestUserDir") + certdir = urljoin(testdir, "signing-keys") + # this sets GNUPGHOME so do it before starting soffice + pyuno.private_initTestEnvironmentGPG(certdir) + # ugly: set var here again because "os.environ" is a copy :( + os.environ["GNUPGHOME"] = url2pathname(urlparse(certdir).path) + super().setUp() + + def tearDown(self): + super().tearDown() + pyuno.private_deinitTestEnvironmentGPG() + def test_gpg_encrypt(self): # TODO: Maybe deduplicate with sw/qa/uitest/writer_tests8/save_with_password_test_policy.py with TemporaryDirectory() as tempdir: diff --git a/xmlsecurity/qa/unit/signing/signing.cxx b/xmlsecurity/qa/unit/signing/signing.cxx index 9f303e0286ff..25da7b3eb334 100644 --- a/xmlsecurity/qa/unit/signing/signing.cxx +++ b/xmlsecurity/qa/unit/signing/signing.cxx @@ -92,7 +92,7 @@ void SigningTest::setUp() UnoApiXmlTest::setUp(); MacrosTest::setUpX509(m_directories, u"xmlsecurity_signing"_ustr); - MacrosTest::setUpGpg(m_directories, u"xmlsecurity_signing"_ustr); + MacrosTest::setUpGpg(m_directories, std::u16string_view(u"xmlsecurity_signing")); // Initialize crypto after setting up the environment variables. mxSEInitializer = xml::crypto::SEInitializer::create(m_xContext); diff --git a/xmlsecurity/qa/unit/signing/signing2.cxx b/xmlsecurity/qa/unit/signing/signing2.cxx index c84fa0b1051a..e4b7aa89f881 100644 --- a/xmlsecurity/qa/unit/signing/signing2.cxx +++ b/xmlsecurity/qa/unit/signing/signing2.cxx @@ -57,7 +57,7 @@ void SigningTest2::setUp() UnoApiXmlTest::setUp(); MacrosTest::setUpX509(m_directories, u"xmlsecurity_signing2"_ustr); - MacrosTest::setUpGpg(m_directories, u"xmlsecurity_signing2"_ustr); + MacrosTest::setUpGpg(m_directories, std::u16string_view(u"xmlsecurity_signing2")); // Initialize crypto after setting up the environment variables. mxSEInitializer = xml::crypto::SEInitializer::create(m_xContext);