module; export module lsp.test.args_parser; import spdlog; import std; import lsp.utils.args_parser; namespace { void Expect(bool condition, std::string_view message) { if (!condition) throw std::runtime_error(std::string(message)); } auto Parse(std::initializer_list arguments) { std::vector storage{ "tsl-server" }; storage.reserve(arguments.size() + 1); for (auto argument : arguments) storage.emplace_back(argument); std::vector argv; argv.reserve(storage.size()); for (auto& argument : storage) argv.push_back(argument.data()); return lsp::utils::ParseArgs(static_cast(argv.size()), argv.data()); } void ExpectError(std::initializer_list arguments, std::string_view fragment) { auto result = Parse(arguments); Expect(!result.has_value(), "arguments should be rejected"); Expect(result.error().find(fragment) != std::string::npos, "error should identify the invalid argument"); } } export int Run() { int failures = 0; auto RunCase = [&failures](std::string_view name, auto test) { try { test(); std::cout << "[PASS] " << name << '\n'; } catch (const std::exception& error) { ++failures; std::cout << "[FAIL] " << name << ": " << error.what() << '\n'; } }; RunCase("defaults", [] { auto result = Parse({}); Expect(result.has_value(), "default arguments should parse"); Expect(result->action == lsp::utils::ParseAction::kRun, "default action should run"); Expect(result->config.thread_count == 4, "default thread count should be 4"); Expect(result->config.log_level == spdlog::level::info, "default log level should be info"); }); RunCase("valid values and last occurrence", [] { auto result = Parse({ "--threads=8", "--threads=16", "--log=debug", "--log-file=server.log", "--interpreter=/opt/tsl" }); Expect(result.has_value(), "valid arguments should parse"); Expect(result->config.thread_count == 16, "last thread count should win"); Expect(result->config.log_level == spdlog::level::debug, "debug log level should parse"); Expect(result->config.log_file == "server.log", "log file should parse"); Expect(result->config.interpreter_path == "/opt/tsl", "interpreter path should parse"); }); RunCase("all log levels", [] { const std::array levels{ std::pair{ "trace", spdlog::level::trace }, std::pair{ "debug", spdlog::level::debug }, std::pair{ "info", spdlog::level::info }, std::pair{ "warn", spdlog::level::warn }, std::pair{ "error", spdlog::level::err }, std::pair{ "off", spdlog::level::off }, }; for (const auto& [name, level] : levels) { const std::string argument = std::string("--log=") + name; auto result = Parse({ argument }); Expect(result.has_value(), "known log level should parse"); Expect(result->config.log_level == level, "log level should match"); } }); RunCase("help takes precedence", [] { auto result = Parse({ "--unknown", "--help", "--threads=bad" }); Expect(result.has_value(), "help should bypass other validation"); Expect(result->action == lsp::utils::ParseAction::kShowHelp, "help action should be returned"); }); RunCase("thread validation", [] { Expect(Parse({ "--threads=1" }).has_value(), "one thread should parse"); Expect(Parse({ "--threads=256" }).has_value(), "256 threads should parse"); for (auto argument : { "--threads=", "--threads=0", "--threads=-1", "--threads=257", "--threads=12junk", "--threads=18446744073709551616" }) ExpectError({ argument }, "--threads"); }); RunCase("other invalid values", [] { for (auto argument : { "--log=verbose", "--log-file=", "--interpreter=", "--log-stdout", "--log-stderr", "--use-stdio", "--unknown" }) ExpectError({ argument }, "--"); }); RunCase("help formatting", [] { std::ostringstream output; lsp::utils::PrintHelp(output, "tsl-server"); const auto text = output.str(); Expect(text.contains("Usage: tsl-server [options]\n\nOptions:\n"), "help should contain physical line breaks"); Expect(!text.contains("\\n"), "help should not contain escaped newline text"); Expect(!text.contains("--log-stdout"), "removed stdout flag should be absent"); Expect(!text.contains("--log-stderr"), "removed stderr flag should be absent"); Expect(!text.contains("--use-stdio"), "removed stdio alias should be absent"); }); std::cout << "Failures: " << failures << '\n'; return failures == 0 ? 0 : 1; }