diff --git a/libc/test/utils/CMakeLists.txt b/libc/test/utils/CMakeLists.txt --- a/libc/test/utils/CMakeLists.txt +++ b/libc/test/utils/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(FPUtil) add_subdirectory(CPP) +add_subdirectory(tools) diff --git a/libc/test/utils/tools/CMakeLists.txt b/libc/test/utils/tools/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/test/utils/tools/CMakeLists.txt @@ -0,0 +1,36 @@ +add_custom_target(libc-tool-util-tests) + +function(add_libc_tool_unittest target_name) + + cmake_parse_arguments( + "LIBC_TOOL_UNITTEST" + "" # No optional arguments + "" # Single value arguments + "SRCS;DEPENDS;ARGS" # Multi-value arguments + ${ARGN} + ) + + add_executable(${target_name} + EXCLUDE_FROM_ALL + ${LIBC_TOOL_UNITTEST_SRCS} + ) + target_link_libraries(${target_name} + PRIVATE + gtest_main + gtest + ${LIBC_TOOL_UNITTEST_DEPENDS} + ) + + add_custom_command( + TARGET ${target_name} + POST_BUILD + COMMAND $ + ${LIBC_TOOL_UNITTEST_ARGS} + ) + add_dependencies(libc-tool-util-tests ${target_name}) + + target_compile_options(${target_name} PUBLIC -fno-rtti) + target_link_libraries(${target_name} PRIVATE LLVMSupport) +endfunction() + +add_subdirectory(WrapperGen) diff --git a/libc/test/utils/tools/WrapperGen/CMakeLists.txt b/libc/test/utils/tools/WrapperGen/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/test/utils/tools/WrapperGen/CMakeLists.txt @@ -0,0 +1,10 @@ +add_libc_tool_unittest( wrappergen_test + SRCS + wrappergen_test.cpp + ARGS + --path=${LIBC_SOURCE_DIR} + --tool=${CMAKE_BINARY_DIR}/bin/libc-wrappergen + --api=${LIBC_SOURCE_DIR}/test/utils/tools/WrapperGen/testapi.td +) + +add_dependencies(wrappergen_test libc-wrappergen) diff --git a/libc/test/utils/tools/WrapperGen/testapi.td b/libc/test/utils/tools/WrapperGen/testapi.td new file mode 100644 --- /dev/null +++ b/libc/test/utils/tools/WrapperGen/testapi.td @@ -0,0 +1,2 @@ +include "spec/spec.td" +include "spec/stdc.td" diff --git a/libc/test/utils/tools/WrapperGen/wrappergen_test.cpp b/libc/test/utils/tools/WrapperGen/wrappergen_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/utils/tools/WrapperGen/wrappergen_test.cpp @@ -0,0 +1,247 @@ +//===-- Unittests for WrapperGen ------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/raw_ostream.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include + +llvm::cl::opt + LibcPath("path", llvm::cl::desc("Path to the top level libc directory."), + llvm::cl::value_desc(""), llvm::cl::Required); +llvm::cl::opt + ToolPath("tool", llvm::cl::desc("Path to the tool executable."), + llvm::cl::value_desc(""), llvm::cl::Required); +llvm::cl::opt + APIPath("api", + llvm::cl::desc("Path to the api tablegen file used by the tests."), + llvm::cl::value_desc(""), llvm::cl::Required); + +class WrapperGenTest : public ::testing::Test { +public: + std::string IncludeArg; + std::string APIArg; + llvm::StringRef ProgPath; + llvm::Expected STDOutFile = + llvm::sys::fs::TempFile::create("wrappergen-stdout-%%-%%-%%-%%.txt"); + llvm::Expected STDErrFile = + llvm::sys::fs::TempFile::create("wrappergen-stderr-%%-%%-%%-%%.txt"); + +protected: + void SetUp() override { + IncludeArg = "-I="; + IncludeArg.append(LibcPath); + APIArg = APIPath; + ProgPath = llvm::StringRef(ToolPath); + + if (!STDOutFile) { + llvm::errs() << "Error: " << llvm::toString(STDOutFile.takeError()) + << "\n"; + llvm::report_fatal_error( + "Temporary file failed to initialize for libc-wrappergen tests."); + } + if (!STDErrFile) { + llvm::errs() << "Error: " << llvm::toString(STDErrFile.takeError()) + << "\n"; + llvm::report_fatal_error( + "Temporary file failed to initialize for libc-wrappergen tests."); + } + } + void TearDown() override { + llvm::consumeError(STDOutFile.get().discard()); + llvm::consumeError(STDErrFile.get().discard()); + } +}; + +TEST_F(WrapperGenTest, RunWrapperGenAndGetNoErrors) { + llvm::Optional Redirects[] = { + llvm::None, llvm::StringRef(STDOutFile.get().TmpName), + llvm::StringRef(STDErrFile.get().TmpName)}; + + llvm::StringRef ArgV[] = {ProgPath, llvm::StringRef(IncludeArg), + llvm::StringRef(APIArg), "--name", "strlen"}; + + int ExitCode = + llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects); + + EXPECT_EQ(ExitCode, 0); + + auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName); + std::string STDErrOutput = STDErrOrError.get()->getBuffer().str(); + ASSERT_EQ(STDErrOutput, ""); +} + +TEST_F(WrapperGenTest, RunWrapperGenOnStrlen) { + llvm::Optional Redirects[] = { + llvm::None, llvm::StringRef(STDOutFile.get().TmpName), + llvm::StringRef(STDErrFile.get().TmpName)}; + + llvm::StringRef ArgV[] = {ProgPath, llvm::StringRef(IncludeArg), + llvm::StringRef(APIArg), "--name", "strlen"}; + + int ExitCode = + llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects); + + EXPECT_EQ(ExitCode, 0); + + auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName); + std::string STDErrOutput = STDErrOrError.get()->getBuffer().str(); + + ASSERT_EQ(STDErrOutput, ""); + + auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName); + std::string STDOutOutput = STDOutOrError.get()->getBuffer().str(); + + ASSERT_EQ(STDOutOutput, "#include \"src/string/strlen.h\"\n" + "extern \"C\" size_t strlen(const char * __arg0) {\n" + " return __llvm_libc::strlen(__arg0);\n" + "}\n"); + // TODO:(michaelrj) Figure out how to make this output comparison + // less brittle. Currently it's just comparing the output of the program + // to an exact string, this means that even a small formatting change + // would break this test. +} + +TEST_F(WrapperGenTest, RunWrapperGenOnStrlenWithAliasee) { + llvm::Optional Redirects[] = { + llvm::None, llvm::StringRef(STDOutFile.get().TmpName), + llvm::StringRef(STDErrFile.get().TmpName)}; + + llvm::StringRef ArgV[] = {ProgPath, + llvm::StringRef(IncludeArg), + llvm::StringRef(APIArg), + "--aliasee", + "STRLEN_ALIAS", + "--name", + "strlen"}; + + int ExitCode = + llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects); + + EXPECT_EQ(ExitCode, 0); + + auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName); + std::string STDErrOutput = STDErrOrError.get()->getBuffer().str(); + + ASSERT_EQ(STDErrOutput, ""); + + auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName); + std::string STDOutOutput = STDOutOrError.get()->getBuffer().str(); + + ASSERT_EQ(STDOutOutput, "extern \"C\" size_t strlen(const char * __arg0) " + "__attribute__((alias(\"STRLEN_ALIAS\")));\n"); + // TODO:(michaelrj) Figure out how to make this output comparison + // less brittle. Currently it's just comparing the output of the program + // to an exact string, this means that even a small formatting change + // would break this test. +} + +///////////////////////////////////////////////////////////////////// +// BAD INPUT TESTS +// all of the tests after this point are testing inputs that should +// return errors +///////////////////////////////////////////////////////////////////// + +TEST_F(WrapperGenTest, + RunWrapperGenOnStrlenWithAliaseeAndAliaseeFileWhichIsError) { + llvm::Optional Redirects[] = { + llvm::None, llvm::StringRef(STDOutFile.get().TmpName), + llvm::StringRef(STDErrFile.get().TmpName)}; + + llvm::StringRef ArgV[] = {ProgPath, + llvm::StringRef(IncludeArg), + llvm::StringRef(APIArg), + "--aliasee", + "STRLEN_ALIAS", + "--aliasee-file", + "STRLEN_ALIAS_FILE", + "--name", + "strlen"}; + + int ExitCode = + llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects); + + EXPECT_EQ(ExitCode, 1); + + auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName); + std::string STDErrOutput = STDErrOrError.get()->getBuffer().str(); + + ASSERT_EQ(STDErrOutput, "error: The options 'aliasee' and 'aliasee-file' " + "cannot be specified simultaniously.\n"); + + auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName); + std::string STDOutOutput = STDOutOrError.get()->getBuffer().str(); + + ASSERT_EQ(STDOutOutput, ""); +} + +TEST_F(WrapperGenTest, RunWrapperGenOnBadFuncName) { + llvm::Optional Redirects[] = { + llvm::None, llvm::StringRef(STDOutFile.get().TmpName), + llvm::StringRef(STDErrFile.get().TmpName)}; + + llvm::StringRef BadFuncName = "FAKE_TEST_FUNC"; + + llvm::StringRef ArgV[] = {ProgPath, llvm::StringRef(IncludeArg), + llvm::StringRef(APIArg), "--name", BadFuncName}; + + int ExitCode = + llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects); + + EXPECT_EQ(ExitCode, 1); + + auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName); + std::string STDErrOutput = STDErrOrError.get()->getBuffer().str(); + + ASSERT_EQ(STDErrOutput, ("error: Function '" + BadFuncName + + "' not found in any standard spec.\n") + .str()); + + auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName); + std::string STDOutOutput = STDOutOrError.get()->getBuffer().str(); + + ASSERT_EQ(STDOutOutput, ""); +} + +TEST_F(WrapperGenTest, RunWrapperGenOnStrlenWithBadAliaseeFile) { + llvm::Optional Redirects[] = { + llvm::None, llvm::StringRef(STDOutFile.get().TmpName), + llvm::StringRef(STDErrFile.get().TmpName)}; + + llvm::StringRef BadAliaseeFileName = "FILE_THAT_DOESNT_EXIST.txt"; + + llvm::StringRef ArgV[] = { + ProgPath, llvm::StringRef(IncludeArg), llvm::StringRef(APIArg), + "--aliasee-file", BadAliaseeFileName, "--name", + "strlen"}; + + int ExitCode = + llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects); + + EXPECT_EQ(ExitCode, 1); + + auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName); + std::string STDErrOutput = STDErrOrError.get()->getBuffer().str(); + + ASSERT_EQ(STDErrOutput, ("error: Unable to read the aliasee file " + + BadAliaseeFileName + "\n") + .str()); + + auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName); + std::string STDOutOutput = STDOutOrError.get()->getBuffer().str(); + + ASSERT_EQ(STDOutOutput, ""); +}