diff --git a/core/base/src/TPluginManager.cxx b/core/base/src/TPluginManager.cxx index 9a20d9207be80..7e4e71ae2cc91 100644 --- a/core/base/src/TPluginManager.cxx +++ b/core/base/src/TPluginManager.cxx @@ -532,7 +532,8 @@ void TPluginManager::LoadHandlersFromPluginDirs(const char *base) } if (!skip) { if (sbase != "") { - const char *p = gSystem->PrependPathName(d, sbase); + TString temp = sbase; + const char *p = gSystem->PrependPathName(d, temp); LoadHandlerMacros(p); } else { void *dirp = gSystem->OpenDirectory(d); diff --git a/core/base/src/TSystem.cxx b/core/base/src/TSystem.cxx index 9f8d8a83a23a2..bc8ee2454da3d 100644 --- a/core/base/src/TSystem.cxx +++ b/core/base/src/TSystem.cxx @@ -2619,7 +2619,6 @@ static void R__WriteDependencyFile(const TString & build_loc, const TString &dep } else { needToUnlinkTempFile = true; devnullfile = depfilename + ".stderr.tmp"; - gSystem->PrependPathName(build_loc, devnullfile); } } #endif diff --git a/core/base/test/CMakeLists.txt b/core/base/test/CMakeLists.txt index c60aa85eb9992..9528a5c1af67a 100644 --- a/core/base/test/CMakeLists.txt +++ b/core/base/test/CMakeLists.txt @@ -36,3 +36,5 @@ configure_file(Foo.C Foo.C COPYONLY) ROOT_ADD_GTEST(IncludePathTest IncludePathTest.cxx LIBRARIES Core) ROOT_ADD_GTEST(UUIDTest UUIDTest.cxx LIBRARIES Core) + +ROOT_ADD_GTEST(CorePluginManagerTests TPluginManagerTests.cxx LIBRARIES Core) diff --git a/core/base/test/IncludePathTest.cxx b/core/base/test/IncludePathTest.cxx index b5f0fbadb0fd2..e76acedfa68ee 100644 --- a/core/base/test/IncludePathTest.cxx +++ b/core/base/test/IncludePathTest.cxx @@ -1,5 +1,6 @@ #include "gtest/gtest.h" #include "TSystem.h" +#include "TUUID.h" #include @@ -14,15 +15,53 @@ TEST(TSystem, IncludePath) "-I \"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.38.33130\\include\""); #endif +#ifdef WIN32 + struct ComspecGuard { + TString fOriginal; + TString fAlias; + + ~ComspecGuard() + { + gSystem->Setenv("COMSPEC", fOriginal); + gSystem->Unlink(fAlias); + } + }; + + const char *comspec = gSystem->Getenv("COMSPEC"); + ASSERT_NE(comspec, nullptr); + TString cmdPath = comspec; + if (cmdPath.BeginsWith("\"") && cmdPath.EndsWith("\"")) { + cmdPath.Remove(0, 1); + cmdPath.Chop(); + } + TString normalizedCmdPath = cmdPath; + normalizedCmdPath.ToLower(); + ASSERT_TRUE(normalizedCmdPath.EndsWith("cmd.exe")) << "This regression test needs cmd.exe as the process shell"; + + TUUID uuid; + TString alias = TString::Format("root_test_shell_%s.exe", uuid.AsString()); + gSystem->PrependPathName(gSystem->TempDirectory(), alias); + ComspecGuard comspecGuard{comspec, alias}; + ASSERT_EQ(0, gSystem->CopyFile(cmdPath, alias)); + + // Use a copied cmd.exe under a different name to exercise the temporary + // stderr-file path without changing the shell used to execute commands. + gSystem->Setenv("COMSPEC", alias); + ASSERT_EQ(0, gSystem->Exec("exit 0")); +#endif + testing::internal::CaptureStderr(); - gSystem->CompileMacro("Foo.C", "f"); + const auto compileResult = gSystem->CompileMacro("Foo.C", "f"); std::cerr.flush(); std::string errors = testing::internal::GetCapturedStderr(); + EXPECT_TRUE(compileResult); gSystem->Unload("Foo_C"); gSystem->CleanCompiledMacros(); #ifdef WIN32 EXPECT_TRUE((errors.find("cl : Command line warning D9002 : ignoring unknown option ") == std::string::npos)); + // This will need to be updated once we no longer rely on rmkdepend. + EXPECT_EQ(errors.find("Failed to run rmkdepend"), std::string::npos); gSystem->Exec("del Foo_C*"); #else EXPECT_TRUE((errors.find("c++: error: unrecognized command line option ") == std::string::npos)); diff --git a/core/base/test/TPluginManagerTests.cxx b/core/base/test/TPluginManagerTests.cxx new file mode 100644 index 0000000000000..2b84dac61b4d3 --- /dev/null +++ b/core/base/test/TPluginManagerTests.cxx @@ -0,0 +1,98 @@ +#include "gtest/gtest.h" + +#include "TEnv.h" +#include "TPluginManager.h" +#include "TROOT.h" +#include "TString.h" +#include "TSystem.h" +#include "TUUID.h" + +#include +#include + +namespace { + +class TPluginManagerTest : public testing::Test { +protected: + TEnv fEnv; + TPluginManager fPluginManager; + TEnv *fSavedEnv = nullptr; + TPluginManager *fSavedPluginManager = nullptr; + std::vector fPaths; + TString fBase; + + void SetUp() override + { + // Initialize ROOT before replacing the globals used by interpreted plugin macros. + (void)gROOT->GetPluginManager(); + fSavedEnv = gEnv; + fSavedPluginManager = gPluginMgr; + gEnv = &fEnv; + gPluginMgr = &fPluginManager; + + TUUID uuid; + TString id = uuid.AsString(); + id.ReplaceAll("-", "_"); + fBase = "TestPluginBase_" + id; + TString rootDir = "test_plugins_" + id; + gSystem->PrependPathName(gSystem->TempDirectory(), rootDir); + ASSERT_EQ(0, gSystem->mkdir(rootDir)); + fPaths.push_back(rootDir); + + TString pluginPath; + for (int i = 0; i < 3; ++i) { + TString dir = TString::Format("dir%d", i); + gSystem->PrependPathName(rootDir, dir); + ASSERT_EQ(0, gSystem->mkdir(dir)); + fPaths.push_back(dir); + + TString baseDir = fBase; + gSystem->PrependPathName(dir, baseDir); + ASSERT_EQ(0, gSystem->mkdir(baseDir)); + fPaths.push_back(baseDir); + + TString macroName = TString::Format("P010_TestPlugin%d_", i) + id; + TString macroFile = macroName + ".C"; + gSystem->PrependPathName(baseDir, macroFile); + fPaths.push_back(macroFile); + std::ofstream macro(macroFile.Data()); + ASSERT_TRUE(macro.is_open()); + macro << "#include \"TPluginManager.h\"\n" + << "void " << macroName << "()\n{\n" + << " gPluginMgr->AddHandler(\"" << fBase << "\", \"^test" << i << ":\", \"TestPlugin" << i + << "\", \"TestPluginLib\", \"TestPlugin" << i << "()\");\n}\n"; + macro.close(); + ASSERT_TRUE(macro.good()); + + if (i != 0) { +#ifdef R__WIN32 + pluginPath += ";"; +#else + pluginPath += ":"; +#endif + } + pluginPath += dir; + } + fEnv.SetValue("Root.PluginPath", pluginPath); + } + + void TearDown() override + { + gPluginMgr = fSavedPluginManager; + gEnv = fSavedEnv; + for (auto it = fPaths.rbegin(); it != fPaths.rend(); ++it) + EXPECT_EQ(0, gSystem->Unlink(*it)) << *it; + } +}; + +// Regression test for https://github.com/root-project/root/issues/23536. +TEST_F(TPluginManagerTest, MultiDirectoryPluginPath) +{ + for (int i = 0; i < 3; ++i) { + auto *handler = fPluginManager.FindHandler(fBase, TString::Format("test%d:example", i)); + ASSERT_NE(handler, nullptr) << "Plugin directory " << i; + EXPECT_STREQ(handler->GetClass(), TString::Format("TestPlugin%d", i).Data()); + } +} + +} // namespace diff --git a/core/metacling/src/TCling.cxx b/core/metacling/src/TCling.cxx index 022cc1ddba473..a95f35412adf9 100644 --- a/core/metacling/src/TCling.cxx +++ b/core/metacling/src/TCling.cxx @@ -778,8 +778,12 @@ int TCling_GenerateDictionary(const std::vector &classes, while (dirbase.Length() && dirbase != "." && dirbase != "include" && dirbase != "inc" && dirbase != "prec_stl") { + TString parent = gSystem->GetDirName(dir); + if (parent == dir) + break; gSystem->PrependPathName(dirbase, header); - dir = gSystem->GetDirName(dir); + dir = parent; + dirbase = gSystem->BaseName(dir); } fileContent += TString("#include \"") + header + "\"\n"; } diff --git a/core/metacling/test/TClingTests.cxx b/core/metacling/test/TClingTests.cxx index 08f846ee5f256..927dd09af0b99 100644 --- a/core/metacling/test/TClingTests.cxx +++ b/core/metacling/test/TClingTests.cxx @@ -4,6 +4,7 @@ #include "TInterpreter.h" #include "TROOT.h" #include "TSystem.h" +#include "TString.h" #include "gmock/gmock.h" @@ -63,6 +64,24 @@ TEST_F(TClingTests, GenerateDictionaryRegression) EXPECT_TRUE(gInterpreter->GenerateDictionary("std::set", "")); EXPECT_TRUE(gInterpreter->GenerateDictionary("std::set", "set")); + auto *sequentialExecutor = TClass::GetClass("ROOT::TSequentialExecutor"); + ASSERT_NE(sequentialExecutor, nullptr); + TString headerDir = gSystem->GetDirName(sequentialExecutor->GetDeclFileName()); + ASSERT_STREQ(gSystem->BaseName(headerDir), "ROOT"); + + ASSERT_TRUE(gInterpreter->Declare( + "#include \n" + "#include \n" + "int TCling_GenerateDictionary(const std::vector&, const std::vector&,\n" + " const std::vector&, const std::vector&);")); + TString includePath = gSystem->GetIncludePath(); + const auto result = + gInterpreter->ProcessLine("std::vector classes{\"std::list\"};" + "std::vector emptyvector, unknown{\"ROOT::TSequentialExecutor\"};" + "return TCling_GenerateDictionary(classes, emptyvector, emptyvector, unknown);"); + gSystem->SetIncludePath(includePath); + EXPECT_EQ(result, 0); + // FIXME: This makes the linkdef parser go in an infinite loop. //EXPECT_TRUE(gInterpreter->GenerateDictionary("std::vector>", "")); }