// Подставная «java» для тестов. Без Qt, чтобы её копию можно было положить // в любую папку и запустить без поиска библиотек Qt. // // Поведение задаётся файлом fake-java.cfg рядом с исполняемым файлом, // по строке «ключ=значение»: // version=21.0.2 что печатать на «-version» (в stderr, как настоящая java) // exit=0 код выхода обычного запуска // sleep_ms=0 сколько «работать» перед выходом // args_file=<путь> куда записать аргументы, по одному на строку // dump_env=<ИМЯ> дописать в args_file строку «env:ИМЯ=значение» (можно несколько) // stdout=<строка> что напечатать в stdout (можно несколько) // create=<путь>\t<текст> создать файл с текстом; относительный путь — от рабочей папки // crash=1 завершиться аварийно (abort) #include #include #include #include #include #include #include #include #include #if defined(_WIN32) #include #elif defined(__APPLE__) #include #endif namespace fs = std::filesystem; namespace { fs::path executablePath(const char *argv0) { #if defined(_WIN32) wchar_t buffer[MAX_PATH * 4]; const DWORD size = GetModuleFileNameW(nullptr, buffer, DWORD(std::size(buffer))); return fs::path(std::wstring(buffer, size)); #elif defined(__APPLE__) char buffer[4096]; uint32_t size = sizeof(buffer); if (_NSGetExecutablePath(buffer, &size) == 0) return fs::canonical(buffer); return fs::absolute(argv0); #else std::error_code ec; const fs::path self = fs::read_symlink("/proc/self/exe", ec); return ec ? fs::absolute(argv0) : self; #endif } } // namespace int main(int argc, char *argv[]) { std::string version = "17.0.8"; int exitCode = 0; int sleepMs = 0; bool crash = false; std::string argsFile; std::vector dumpEnv; std::vector stdoutLines; std::vector> creates; std::ifstream config(executablePath(argv[0]).parent_path() / "fake-java.cfg"); for (std::string line; std::getline(config, line);) { if (!line.empty() && line.back() == '\r') line.pop_back(); const auto eq = line.find('='); if (eq == std::string::npos) continue; const std::string key = line.substr(0, eq); const std::string value = line.substr(eq + 1); if (key == "version") version = value; else if (key == "exit") exitCode = std::atoi(value.c_str()); else if (key == "sleep_ms") sleepMs = std::atoi(value.c_str()); else if (key == "crash") crash = value == "1"; else if (key == "args_file") argsFile = value; else if (key == "dump_env") dumpEnv.push_back(value); else if (key == "stdout") stdoutLines.push_back(value); else if (key == "create") { const auto tab = value.find('\t'); creates.emplace_back(value.substr(0, tab), tab == std::string::npos ? std::string() : value.substr(tab + 1)); } } for (int i = 1; i < argc; ++i) { if (std::string(argv[i]) == "-version") { std::cerr << "openjdk version \"" << version << "\" 2024-01-16\n" << "OpenJDK Runtime Environment (fake)\n"; return 0; } } if (!argsFile.empty()) { // binary: в текстовом режиме Windows превратит '\n' в "\r\n", и тесты увидят «-jar\r». std::ofstream out(argsFile, std::ios::trunc | std::ios::binary); for (int i = 1; i < argc; ++i) out << argv[i] << '\n'; for (const std::string &name : dumpEnv) { const char *value = std::getenv(name.c_str()); out << "env:" << name << '=' << (value ? value : "") << '\n'; } } for (const auto &[path, text] : creates) { const fs::path target(path); std::error_code ec; if (target.has_parent_path()) fs::create_directories(target.parent_path(), ec); std::ofstream(target, std::ios::trunc | std::ios::binary) << text; } for (const std::string &line : stdoutLines) std::cout << line << '\n'; std::cout.flush(); if (sleepMs > 0) std::this_thread::sleep_for(std::chrono::milliseconds(sleepMs)); if (crash) { #if defined(_WIN32) // QProcess считает аварией коды 0x8xxxxxxx–0xCxxxxxxx; abort() дал бы код 3. TerminateProcess(GetCurrentProcess(), 0xC0000005); #endif std::abort(); } return exitCode; }