mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 02:59:33 +07:00
641 lines
23 KiB
Rust
641 lines
23 KiB
Rust
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
|
|
|
use std::{env, fs, thread, time};
|
|
use std::collections::HashMap;
|
|
use std::fmt::{Debug, Formatter};
|
|
use std::fs::{File, OpenOptions};
|
|
use std::io::{BufReader, Read, Write};
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::{Command, ExitStatus, Stdio};
|
|
use std::sync::Once;
|
|
|
|
use anyhow::{bail, Context, Result};
|
|
use log::debug;
|
|
use serde::{Deserialize, Serialize};
|
|
use tempfile::{Builder, TempDir};
|
|
|
|
use xplat_launcher::{DEBUG_MODE_ENV_VAR, PathExt};
|
|
|
|
static INIT: Once = Once::new();
|
|
static mut SHARED: Option<TestEnvironmentShared> = None;
|
|
|
|
#[derive(Clone)]
|
|
pub enum LauncherLocation { Standard, RemoteDev }
|
|
|
|
pub struct TestEnvironment<'a> {
|
|
pub dist_root: PathBuf,
|
|
pub project_dir: PathBuf,
|
|
launcher_path: PathBuf,
|
|
shared_env: &'a TestEnvironmentShared,
|
|
test_root_dir: TempDir,
|
|
to_delete: Vec<PathBuf>
|
|
}
|
|
|
|
impl<'a> TestEnvironment<'a> {
|
|
pub fn create_jbr_link(&self, link_name: &str) -> PathBuf {
|
|
let link = self.dist_root.join(link_name);
|
|
symlink(&self.shared_env.jbr_root, &link).unwrap();
|
|
link
|
|
}
|
|
|
|
pub fn create_launcher_link(&self, link_name: &str) -> PathBuf {
|
|
let link = self.project_dir.join(link_name);
|
|
symlink(&self.launcher_path, &link).unwrap();
|
|
link
|
|
}
|
|
|
|
pub fn create_temp_dir(&self, relative_path: &str) -> PathBuf {
|
|
let temp_dir = self.test_root_dir.path().join(relative_path);
|
|
fs::create_dir_all(&temp_dir).unwrap_or_else(|_| panic!("Cannot create: {:?}", temp_dir));
|
|
temp_dir
|
|
}
|
|
|
|
pub fn create_temp_file(&self, relative_path: &str, content: &str) -> PathBuf {
|
|
let temp_file = self.test_root_dir.path().join(relative_path);
|
|
Self::create_file(&temp_file, content);
|
|
temp_file
|
|
}
|
|
|
|
pub fn create_user_config_file(&mut self, name: &str, content: &str, custom_config_dir: PathBuf) -> PathBuf {
|
|
self.to_delete.push(custom_config_dir.clone());
|
|
let config_file = custom_config_dir.join(name);
|
|
Self::create_file(&config_file, content);
|
|
config_file
|
|
}
|
|
|
|
pub fn create_toolbox_vm_options(&mut self, content: &str) -> PathBuf {
|
|
let dist_root = if cfg!(target_os = "macos") { self.dist_root.parent().unwrap() } else { &self.dist_root };
|
|
let vm_options_name = dist_root.file_name().unwrap().to_str().unwrap().to_string() + ".vmoptions";
|
|
let vm_options_file = dist_root.parent().unwrap().join(vm_options_name);
|
|
self.to_delete.push(vm_options_file.clone());
|
|
Self::create_file(&vm_options_file, content);
|
|
vm_options_file
|
|
}
|
|
|
|
fn create_file(file: &Path, content: &str) {
|
|
fs::create_dir_all(file.parent().unwrap())
|
|
.unwrap_or_else(|_| panic!("Cannot create: {:?}", file));
|
|
OpenOptions::new().write(true).create_new(true).open(file)
|
|
.unwrap_or_else(|_| panic!("Cannot create {:?}", &file))
|
|
.write_all(content.as_bytes())
|
|
.unwrap_or_else(|_| panic!("Cannot write {:?}", &file));
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
pub fn to_unc(&self) -> Self {
|
|
self.map_path(Self::convert_to_unc)
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
pub fn to_ns_prefix(&self) -> Self {
|
|
self.map_path(Self::ns_prefix)
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
fn map_path(&self, mapping: fn(&Path) -> PathBuf) -> Self {
|
|
let new_temp_dir = mapping(self.test_root_dir.path().parent().unwrap());
|
|
TestEnvironment {
|
|
dist_root: mapping(&self.dist_root),
|
|
project_dir: mapping(&self.project_dir),
|
|
launcher_path: mapping(&self.launcher_path),
|
|
shared_env: self.shared_env,
|
|
test_root_dir: Builder::new().prefix("xplat_launcher_test_").tempdir_in(new_temp_dir).unwrap(),
|
|
to_delete: Vec::new()
|
|
}
|
|
}
|
|
|
|
// "C:\some\path" -> "\\127.0.0.1\\C$\some\path"
|
|
#[cfg(target_os = "windows")]
|
|
fn convert_to_unc(path: &Path) -> PathBuf {
|
|
assert!(path.has_root(), "Invalid path: {:?}", path);
|
|
let path_str = path.to_str().unwrap();
|
|
PathBuf::from(String::from("\\\\127.0.0.1\\") + &path_str[0..1] + "$" + &path_str[2..])
|
|
}
|
|
|
|
// "C:\some\path" -> "\\?\C:\some\path"
|
|
#[cfg(target_os = "windows")]
|
|
fn ns_prefix(path: &Path) -> PathBuf {
|
|
assert!(path.has_root(), "Invalid path: {:?}", path);
|
|
PathBuf::from(String::from("\\\\?\\") + path.to_str().unwrap())
|
|
}
|
|
}
|
|
|
|
impl<'a> Drop for TestEnvironment<'a> {
|
|
fn drop(&mut self) {
|
|
for path in &self.to_delete {
|
|
debug!("Deleting {:?}", path);
|
|
if let Ok(metadata) = path.symlink_metadata() {
|
|
let result = if metadata.is_dir() { fs::remove_dir_all(path) } else { fs::remove_file(path) };
|
|
result.unwrap_or_else(|_| panic!("cannot delete: {:?}", path))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn prepare_test_env<'a>(launcher_location: LauncherLocation) -> TestEnvironment<'a> {
|
|
prepare_custom_test_env(launcher_location, None, true)
|
|
}
|
|
|
|
pub fn prepare_custom_test_env<'a>(
|
|
launcher_location: LauncherLocation,
|
|
dir_suffix: Option<&str>,
|
|
with_jbr: bool
|
|
) -> TestEnvironment<'a> {
|
|
match prepare_test_env_impl(launcher_location, dir_suffix, with_jbr) {
|
|
Ok(x) => x,
|
|
Err(e) => panic!("Failed to prepare test environment: {:?}", e),
|
|
}
|
|
}
|
|
|
|
struct TestEnvironmentShared {
|
|
launcher_path: PathBuf,
|
|
jbr_root: PathBuf,
|
|
app_jar_path: PathBuf,
|
|
product_info_path: PathBuf,
|
|
vm_options_path: PathBuf,
|
|
#[allow(dead_code)]
|
|
temp_dir: TempDir
|
|
}
|
|
|
|
fn prepare_test_env_impl<'a>(
|
|
launcher_location: LauncherLocation,
|
|
dir_suffix: Option<&str>,
|
|
with_jbr: bool
|
|
) -> Result<TestEnvironment<'a>> {
|
|
INIT.call_once(|| {
|
|
let shared = init_test_environment_once().context("Failed to init shared test environment").unwrap();
|
|
unsafe {
|
|
SHARED = Some(shared)
|
|
}
|
|
});
|
|
|
|
let shared_env = unsafe { SHARED.as_ref() }.expect("Shared test environment should have already been initialized");
|
|
|
|
let prefix = if let Some(s) = dir_suffix { format!("launcher_test_{s}_") } else { "launcher_test_".to_string() };
|
|
let temp_dir = Builder::new().prefix(&prefix).tempdir().context("Failed to create temp directory")?;
|
|
let temp_path = temp_dir.path().canonicalize()?.strip_ns_prefix()?;
|
|
|
|
let (dist_root, launcher_path) = layout_launcher(launcher_location, with_jbr, &temp_path, shared_env)?;
|
|
|
|
let project_dir = temp_path.join("_project");
|
|
fs::create_dir_all(&project_dir)?;
|
|
|
|
Ok(TestEnvironment { dist_root, project_dir, launcher_path, shared_env, test_root_dir: temp_dir, to_delete: Vec::new() })
|
|
}
|
|
|
|
fn init_test_environment_once() -> Result<TestEnvironmentShared> {
|
|
//xplat_launcher::mini_logger::init(log::LevelFilter::Debug)?;
|
|
|
|
// clean environment variables
|
|
env::remove_var("JDK_HOME");
|
|
env::remove_var("JAVA_HOME");
|
|
|
|
let project_root = env::current_dir().expect("Failed to get project root");
|
|
|
|
let bin_name = if cfg!(target_os = "windows") { "xplat-launcher.exe" } else { "xplat-launcher" };
|
|
let launcher_file = env::current_exe()?.parent_or_err()?.parent_or_err()?.join(bin_name);
|
|
if !launcher_file.exists() {
|
|
bail!("Didn't find source launcher to layout, expected path: {:?}", launcher_file);
|
|
}
|
|
|
|
let gradle_build_dir = Path::new("./resources/TestProject/build");
|
|
if !gradle_build_dir.is_dir() {
|
|
bail!("Missing: {:?}; please run `gradlew :downloadJbr :fatJar` first", gradle_build_dir);
|
|
}
|
|
let gradle_build_dir = gradle_build_dir.canonicalize()?.strip_ns_prefix()?;
|
|
let jbr_root = gradle_build_dir.join("jbr");
|
|
let app_jar_file = gradle_build_dir.join("libs/app.jar");
|
|
let product_info_path = project_root.join(format!("resources/product_info_{}.json", env::consts::OS));
|
|
let vm_options_path = project_root.join("resources/xplat.vmoptions");
|
|
|
|
// on build agents, a temp directory may reside in a different filesystem, so copies are necessary for later linking
|
|
let temp_dir = Builder::new().prefix("xplat_launcher_shared_").tempdir().context("Failed to create temp directory")?;
|
|
let launcher_path = temp_dir.path().join(launcher_file.file_name().unwrap());
|
|
fs::copy(&launcher_file, &launcher_path).with_context(|| format!("Failed to copy {launcher_file:?} to {launcher_path:?}"))?;
|
|
let app_jar_path = temp_dir.path().join(app_jar_file.file_name().unwrap());
|
|
fs::copy(&app_jar_file, &app_jar_path).with_context(|| format!("Failed to copy {app_jar_file:?} to {app_jar_path:?}"))?;
|
|
|
|
Ok(TestEnvironmentShared { launcher_path, jbr_root, app_jar_path, product_info_path, vm_options_path, temp_dir })
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
fn layout_launcher(
|
|
launcher_location: LauncherLocation,
|
|
include_jbr: bool,
|
|
target_dir: &Path,
|
|
shared_env: &TestEnvironmentShared
|
|
) -> Result<(PathBuf, PathBuf)> {
|
|
// .
|
|
// └── XPlatLauncher
|
|
// ├── bin/
|
|
// │ └── xplat-launcher | remote-dev-server
|
|
// │ └── xplat64.vmoptions
|
|
// │ └── idea.properties
|
|
// ├── lib/
|
|
// │ └── app.jar
|
|
// │ └── boot-linux.jar
|
|
// ├── jbr/
|
|
// └── product-info.json
|
|
|
|
let launcher_rel_path = match launcher_location {
|
|
LauncherLocation::Standard => "bin/xplat-launcher",
|
|
LauncherLocation::RemoteDev => "bin/remote-dev-server"
|
|
};
|
|
let dist_root = target_dir.join("XPlatLauncher");
|
|
|
|
layout_launcher_impl(
|
|
&dist_root,
|
|
vec![
|
|
"bin/idea.properties",
|
|
"lib/boot-linux.jar"
|
|
],
|
|
vec![
|
|
(&shared_env.launcher_path, launcher_rel_path),
|
|
(&shared_env.app_jar_path, "lib/app.jar")
|
|
],
|
|
vec![
|
|
(&shared_env.vm_options_path, "bin/xplat64.vmoptions"),
|
|
(&shared_env.product_info_path, "product-info.json")
|
|
],
|
|
include_jbr,
|
|
&shared_env.jbr_root
|
|
)?;
|
|
|
|
let launcher_path = dist_root.join(launcher_rel_path);
|
|
Ok((dist_root, launcher_path))
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
fn layout_launcher(
|
|
launcher_location: LauncherLocation,
|
|
include_jbr: bool,
|
|
target_dir: &Path,
|
|
shared_env: &TestEnvironmentShared
|
|
) -> Result<(PathBuf, PathBuf)> {
|
|
// .
|
|
// └── XPlatLauncher.app
|
|
// └── Contents
|
|
// ├── bin/
|
|
// │ └── remote-dev-server [::RemoteDev]
|
|
// │ └── xplat.vmoptions
|
|
// │ └── idea.properties
|
|
// ├── MacOS/
|
|
// │ └── xplat-launcher [::Standard]
|
|
// ├── Resources/
|
|
// │ └── product-info.json
|
|
// ├── lib/
|
|
// │ └── app.jar
|
|
// │ └── boot-macos.jar
|
|
// ├── jbr/
|
|
// └── Info.plist
|
|
|
|
let launcher_rel_path = match launcher_location {
|
|
LauncherLocation::Standard => "MacOS/xplat-launcher",
|
|
LauncherLocation::RemoteDev => "bin/remote-dev-server"
|
|
};
|
|
let dist_root = target_dir.join("XPlatLauncher.app/Contents");
|
|
let info_plist_path = shared_env.vm_options_path.parent_or_err()?.join("Info.plist");
|
|
|
|
layout_launcher_impl(
|
|
&dist_root,
|
|
vec![
|
|
"bin/idea.properties",
|
|
"lib/boot-macos.jar"
|
|
],
|
|
vec![
|
|
(&shared_env.launcher_path, launcher_rel_path),
|
|
(&shared_env.app_jar_path, "lib/app.jar")
|
|
],
|
|
vec![
|
|
(&shared_env.vm_options_path, "bin/xplat.vmoptions"),
|
|
(&shared_env.product_info_path, "Resources/product-info.json"),
|
|
(&info_plist_path, "Info.plist")
|
|
],
|
|
include_jbr,
|
|
&shared_env.jbr_root
|
|
)?;
|
|
|
|
let launcher_path = dist_root.join(launcher_rel_path);
|
|
Ok((dist_root, launcher_path))
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
fn layout_launcher(
|
|
launcher_location: LauncherLocation,
|
|
include_jbr: bool,
|
|
target_dir: &Path,
|
|
shared_env: &TestEnvironmentShared
|
|
) -> Result<(PathBuf, PathBuf)> {
|
|
// .
|
|
// └── XPlatLauncher
|
|
// ├── bin/
|
|
// │ └── xplat64.exe | remote-dev-server.exe
|
|
// │ └── xplat64.exe.vmoptions
|
|
// │ └── idea.properties
|
|
// ├── lib/
|
|
// │ └── app.jar
|
|
// │ └── boot-windows.jar
|
|
// ├── jbr/
|
|
// └── product-info.json
|
|
|
|
let launcher_rel_path = match launcher_location {
|
|
LauncherLocation::Standard => "bin\\xplat64.exe",
|
|
LauncherLocation::RemoteDev => "bin\\remote-dev-server.exe"
|
|
};
|
|
let dist_root = target_dir.join("XPlatLauncher");
|
|
|
|
layout_launcher_impl(
|
|
&dist_root,
|
|
vec![
|
|
"bin\\idea.properties",
|
|
"lib\\boot-windows.jar"
|
|
],
|
|
vec![
|
|
(&shared_env.launcher_path, launcher_rel_path),
|
|
(&shared_env.app_jar_path, "lib\\app.jar")
|
|
],
|
|
vec![
|
|
(&shared_env.vm_options_path, "bin\\xplat64.exe.vmoptions"),
|
|
(&shared_env.product_info_path, "product-info.json")
|
|
],
|
|
include_jbr,
|
|
&shared_env.jbr_root
|
|
)?;
|
|
|
|
let launcher_path = dist_root.join(launcher_rel_path);
|
|
Ok((dist_root, launcher_path))
|
|
}
|
|
|
|
fn layout_launcher_impl(
|
|
target_dir: &Path,
|
|
create_files: Vec<&str>,
|
|
link_files: Vec<(&Path, &str)>,
|
|
copy_files: Vec<(&Path, &str)>,
|
|
include_jbr: bool,
|
|
jbr_path: &Path
|
|
) -> Result<()> {
|
|
for target_rel_path in create_files {
|
|
let target = &target_dir.join(target_rel_path);
|
|
fs::create_dir_all(target.parent_or_err()?).with_context(|| format!("Failed to create dir {:?}", target.parent()))?;
|
|
File::create(target).with_context(|| format!("Failed to create file {target:?}"))?;
|
|
}
|
|
|
|
for (source, target_rel_path) in link_files {
|
|
let target = &target_dir.join(target_rel_path);
|
|
fs::create_dir_all(target.parent_or_err()?)?;
|
|
fs::hard_link(source, target).with_context(|| format!("Failed to create hardlink {target:?} -> {source:?}"))?;
|
|
}
|
|
|
|
for (source, target_rel_path) in copy_files {
|
|
let target = &target_dir.join(target_rel_path);
|
|
fs::create_dir_all(target.parent_or_err()?)?;
|
|
fs::copy(source, target).with_context(|| format!("Failed to copy {source:?} to {target:?}"))?;
|
|
}
|
|
|
|
if include_jbr {
|
|
symlink(jbr_path, &target_dir.join("jbr"))?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(target_family = "unix")]
|
|
fn symlink(original: &Path, link: &Path) -> Result<()> {
|
|
std::os::unix::fs::symlink(original, link).with_context(|| format!("Failed to create symlink {link:?} -> {original:?}"))?;
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
fn symlink(original: &Path, link: &Path) -> Result<()> {
|
|
let result = match original.is_dir() {
|
|
true => std::os::windows::fs::symlink_dir(original, link),
|
|
false => std::os::windows::fs::symlink_file(original, link)
|
|
};
|
|
|
|
let message = match &result {
|
|
Ok(_) => "",
|
|
Err(e) if e.raw_os_error() == Some(1314) => "Cannot use CreateSymbolicLink.\
|
|
Consider having a privilege to do that or enabling Developer Mode",
|
|
Err(_) => "Failed to create a symlink for a reason unrelated to privileges",
|
|
};
|
|
|
|
result.with_context(|| format!("Failed to create symlink {link:?} -> {original:?}; {message}"))
|
|
}
|
|
|
|
pub struct LauncherRunSpec {
|
|
location: LauncherLocation,
|
|
dump: bool,
|
|
assert_status: bool,
|
|
args: Vec<String>,
|
|
env: HashMap<String, String>
|
|
}
|
|
|
|
impl LauncherRunSpec {
|
|
pub fn standard() -> LauncherRunSpec {
|
|
LauncherRunSpec {
|
|
location: LauncherLocation::Standard, dump: false, assert_status: false, args: Vec::new(), env: HashMap::new()
|
|
}
|
|
}
|
|
|
|
pub fn remote_dev() -> LauncherRunSpec {
|
|
LauncherRunSpec {
|
|
location: LauncherLocation::RemoteDev, dump: false, assert_status: false, args: Vec::new(), env: HashMap::new()
|
|
}
|
|
}
|
|
|
|
pub fn with_dump(&mut self) -> &mut Self {
|
|
self.dump = true;
|
|
self
|
|
}
|
|
|
|
pub fn assert_status(&mut self) -> &mut Self {
|
|
self.assert_status = true;
|
|
self
|
|
}
|
|
|
|
pub fn with_args(&mut self, args: &[&str]) -> &mut Self {
|
|
self.args.extend(args.iter().map(|s| s.to_string()));
|
|
self
|
|
}
|
|
|
|
pub fn with_env(&mut self, env: &HashMap<&str, &str>) -> &mut Self {
|
|
self.env.extend(env.iter().map(|(k, v)| (k.to_string(), v.to_string())));
|
|
self
|
|
}
|
|
}
|
|
|
|
pub struct LauncherRunResult {
|
|
pub exit_status: ExitStatus,
|
|
pub stdout: String,
|
|
pub stderr: String,
|
|
dump: Option<Result<IntellijMainDumpedLaunchParameters>>,
|
|
}
|
|
|
|
impl LauncherRunResult {
|
|
pub fn dump(self) -> IntellijMainDumpedLaunchParameters {
|
|
let err_message = format!("Dump was not collected: {:?}", self);
|
|
match self.dump {
|
|
Some(result) => result.expect(&err_message),
|
|
None => panic!("Dump was not requested; add `.with_dump()` to the run specification")
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Debug for LauncherRunResult {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
f.write_fmt(format_args!(
|
|
"\n** exit code: {:?} ({:?})\n** stderr: <<<{}>>>\n** stdout: <<<{}>>>",
|
|
self.exit_status.code(), self.exit_status, self.stderr, self.stdout))
|
|
}
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
|
pub struct IntellijMainDumpedLaunchParameters {
|
|
pub cmdArguments: Vec<String>,
|
|
pub vmOptions: Vec<String>,
|
|
pub environmentVariables: HashMap<String, String>,
|
|
pub systemProperties: HashMap<String, String>
|
|
}
|
|
|
|
pub fn run_launcher(run_spec: &LauncherRunSpec) -> LauncherRunResult {
|
|
let test_env = prepare_test_env(run_spec.location.clone());
|
|
run_launcher_ext(&test_env, run_spec)
|
|
}
|
|
|
|
pub fn run_launcher_ext(test_env: &TestEnvironment, run_spec: &LauncherRunSpec) -> LauncherRunResult {
|
|
match run_launcher_impl(test_env, run_spec) {
|
|
Ok(result) => {
|
|
if run_spec.assert_status {
|
|
assert!(result.exit_status.success(), "The exit status of the launcher is not successful: {:?}", result);
|
|
}
|
|
result
|
|
}
|
|
Err(e) => {
|
|
panic!("Failed to get launcher run result: {:?}", e)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn run_launcher_impl(test_env: &TestEnvironment, run_spec: &LauncherRunSpec) -> Result<LauncherRunResult> {
|
|
debug!("Starting '{}'\n with args {:?}\n in '{}'",
|
|
test_env.launcher_path.display(), run_spec.args, test_env.test_root_dir.path().display());
|
|
|
|
let stdout_file_path = &test_env.test_root_dir.path().join("out.txt");
|
|
let stderr_file_path = &test_env.test_root_dir.path().join("err.txt");
|
|
let project_dir = test_env.project_dir.to_str().unwrap();
|
|
let dump_file_path = test_env.test_root_dir.path().join("output.json");
|
|
let dump_file_path_str = dump_file_path.strip_ns_prefix()?.to_string_checked()?;
|
|
|
|
let mut full_args = Vec::<&str>::new();
|
|
if run_spec.dump {
|
|
let dump_args = match run_spec.location {
|
|
LauncherLocation::Standard => vec!["dump-launch-parameters", "--output", &dump_file_path_str],
|
|
LauncherLocation::RemoteDev => vec!["dumpLaunchParameters", project_dir, "--output", &dump_file_path_str]
|
|
};
|
|
for arg in dump_args {
|
|
full_args.push(arg);
|
|
}
|
|
}
|
|
for arg in run_spec.args.iter() {
|
|
full_args.push(arg);
|
|
}
|
|
|
|
let mut full_env = match run_spec.location {
|
|
LauncherLocation::Standard => HashMap::from([
|
|
(DEBUG_MODE_ENV_VAR, "1")
|
|
]),
|
|
LauncherLocation::RemoteDev => HashMap::from([
|
|
("CWM_NO_PASSWORD", "1"),
|
|
("CWM_HOST_PASSWORD", "1"),
|
|
("REMOTE_DEV_NON_INTERACTIVE", "1"),
|
|
("IJ_HOST_CONFIG_DIR", project_dir),
|
|
("IJ_HOST_SYSTEM_DIR", project_dir),
|
|
("IJ_HOST_LOGS_DIR", project_dir)
|
|
]),
|
|
};
|
|
for (k, v) in run_spec.env.iter() {
|
|
full_env.insert(k, v);
|
|
}
|
|
|
|
let stdout_file = File::create(stdout_file_path)
|
|
.context(format!("Failed to create stdout file at {stdout_file_path:?}"))?;
|
|
|
|
let stderr_file = File::create(stderr_file_path)
|
|
.context(format!("Failed to create stderr file at {stderr_file_path:?}"))?;
|
|
|
|
let mut launcher_process = Command::new(&test_env.launcher_path)
|
|
.current_dir(test_env.test_root_dir.path())
|
|
.args(full_args)
|
|
.stdout(Stdio::from(stdout_file))
|
|
.stderr(Stdio::from(stderr_file))
|
|
.envs(full_env)
|
|
.spawn()
|
|
.context("Failed to spawn launcher process")?;
|
|
|
|
let started = time::Instant::now();
|
|
|
|
loop {
|
|
let elapsed = time::Instant::now() - started;
|
|
if elapsed > time::Duration::from_secs(60) {
|
|
panic!("Launcher has been running for more than 60 seconds, terminating")
|
|
}
|
|
|
|
if let Some(es) = launcher_process.try_wait()? {
|
|
return Ok(LauncherRunResult {
|
|
exit_status: es,
|
|
stdout: read_output_file(stdout_file_path).context("Cannot read stdout file")?,
|
|
stderr: read_output_file(stderr_file_path).context("Cannot read stderr file")?,
|
|
dump: if run_spec.dump { Some(read_launcher_run_result(&dump_file_path)) } else { None }
|
|
});
|
|
}
|
|
|
|
thread::sleep(time::Duration::from_secs(1))
|
|
}
|
|
}
|
|
|
|
fn read_output_file(path: &Path) -> Result<String> {
|
|
let bytes = fs::read(path).with_context(|| format!("Cannot open {:?}", path))?;
|
|
if let Ok(string) = String::from_utf8(bytes.to_owned()) {
|
|
Ok(string)
|
|
} else {
|
|
for line in bytes.split(|b| *b == b'\n') {
|
|
if let Err(e) = String::from_utf8(line.to_owned()) {
|
|
bail!("{}: {:?} {:?}", e, line, String::from_utf8_lossy(line))
|
|
}
|
|
}
|
|
panic!("Should not reach here");
|
|
}
|
|
}
|
|
|
|
fn read_launcher_run_result(path: &Path) -> Result<IntellijMainDumpedLaunchParameters> {
|
|
let file = File::open(path)?;
|
|
let mut reader = BufReader::new(file);
|
|
let mut text = String::new();
|
|
reader.read_to_string(&mut text)?;
|
|
let dump: IntellijMainDumpedLaunchParameters = serde_json::from_str(&text)?;
|
|
Ok(dump)
|
|
}
|
|
|
|
pub fn test_runtime_selection(result: LauncherRunResult, expected_rt: PathBuf) {
|
|
let rt_line = result.stdout.lines()
|
|
.find(|line| line.contains("Resolved runtime: "))
|
|
.unwrap_or_else(|| panic!("The 'Resolved runtime:' line is not in the output: {}", result.stdout));
|
|
let resolved_rt = rt_line.split_once("Resolved runtime: ").unwrap().1;
|
|
let actual_rt = &resolved_rt[1..resolved_rt.len() - 1].replace("\\\\", "\\");
|
|
|
|
let adjusted_rt = if cfg!(target_os = "macos") { expected_rt.join("Contents/Home") } else { expected_rt };
|
|
|
|
assert_eq!(adjusted_rt.to_str().unwrap(), actual_rt, "Wrong runtime; run result: {:?}", result);
|
|
}
|
|
|
|
pub fn assert_vm_option_presence(dump: &IntellijMainDumpedLaunchParameters, vm_option: &str) {
|
|
assert!(dump.vmOptions.contains(&vm_option.to_string()),
|
|
"{:?} is not in {:?}", vm_option, dump.vmOptions);
|
|
}
|
|
|
|
pub fn assert_vm_option_absence(dump: &IntellijMainDumpedLaunchParameters, vm_option: &str) {
|
|
assert!(!dump.vmOptions.contains(&vm_option.to_string()),
|
|
"{:?} should not be in {:?}", vm_option, dump.vmOptions);
|
|
}
|