aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/tests/common/test_case.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe/tests/common/test_case.rs')
-rw-r--r--crates/shirabe/tests/common/test_case.rs274
1 files changed, 274 insertions, 0 deletions
diff --git a/crates/shirabe/tests/common/test_case.rs b/crates/shirabe/tests/common/test_case.rs
index 71c49a9..4902448 100644
--- a/crates/shirabe/tests/common/test_case.rs
+++ b/crates/shirabe/tests/common/test_case.rs
@@ -4,11 +4,25 @@
//! `#[path = "../common/test_case.rs"] mod test_case;`.
#![allow(dead_code)]
+use shirabe::console::application::ApplicationHandle;
use shirabe::package::handle::{
CompleteAliasPackageHandle, CompletePackageHandle, PackageInterfaceHandle,
};
+use shirabe::util::platform::Platform;
+use shirabe_external_packages::symfony::console::input::array_input::ArrayInput;
+use shirabe_external_packages::symfony::console::input::input_interface::InputInterface;
+use shirabe_external_packages::symfony::console::input::streamable_input_interface::StreamableInputInterface;
+use shirabe_external_packages::symfony::console::output::console_output::ConsoleOutput;
+use shirabe_external_packages::symfony::console::output::console_output_interface::ConsoleOutputInterface;
+use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface;
+use shirabe_external_packages::symfony::console::output::stream_output::StreamOutput;
+use shirabe_php_shim::{PhpMixed, PhpResource};
use shirabe_semver::constraint::{AnyConstraint, SimpleConstraint};
use shirabe_semver::version_parser::VersionParser;
+use std::cell::RefCell;
+use std::path::PathBuf;
+use std::rc::Rc;
+use tempfile::TempDir;
/// ref: TestCase::getPackage (default class CompletePackage)
pub fn get_package(name: &str, version: &str) -> PackageInterfaceHandle {
@@ -35,3 +49,263 @@ pub fn get_version_constraint(operator: &str, version: &str) -> AnyConstraint {
Some(format!("{} {}", operator, version)),
))
}
+
+/// ref: TestCase::initTempComposer plus the running TearDown.
+///
+/// Creates a fresh temp dir, chdir()s into it, points `COMPOSER_HOME` at it, and writes
+/// `composer.json`/`auth.json` (and `composer.lock` when given). The returned guard restores the
+/// previous cwd / `COMPOSER_HOME` and removes the temp tree on drop, mirroring PHPUnit's `tearDown`.
+pub struct TearDown {
+ temp_dir: TempDir,
+ prev_cwd: PathBuf,
+ prev_composer_home: Option<String>,
+}
+
+impl TearDown {
+ /// The temp directory created by `init_temp_composer`. Equivalent to the `$dir` returned by PHP.
+ pub fn working_dir(&self) -> PathBuf {
+ self.temp_dir.path().to_path_buf()
+ }
+}
+
+impl Drop for TearDown {
+ fn drop(&mut self) {
+ // Restore the cwd before the TempDir field is dropped so the tree (possibly the cwd itself)
+ // can be removed cleanly, even on a panicking test.
+ let _ = std::env::set_current_dir(&self.prev_cwd);
+ match &self.prev_composer_home {
+ Some(value) => Platform::put_env("COMPOSER_HOME", value),
+ None => Platform::clear_env("COMPOSER_HOME"),
+ }
+ }
+}
+
+/// ref: TestCase::initTempComposer
+pub fn init_temp_composer(
+ composer_json: Option<&serde_json::Value>,
+ auth_json: Option<&serde_json::Value>,
+ composer_lock: Option<&serde_json::Value>,
+ setup_repositories: bool,
+) -> TearDown {
+ let temp_dir = TempDir::new().unwrap();
+ let dir = temp_dir.path().to_path_buf();
+
+ let prev_cwd = std::env::current_dir().unwrap();
+ let prev_composer_home = Platform::get_env("COMPOSER_HOME");
+
+ Platform::put_env("COMPOSER_HOME", &format!("{}/composer-home", dir.display()));
+ Platform::put_env("COMPOSER_DISABLE_XDEBUG_WARN", "1");
+
+ let mut composer_json = composer_json
+ .cloned()
+ .unwrap_or_else(|| serde_json::json!({}));
+ let auth_json = auth_json.cloned().unwrap_or_else(|| serde_json::json!({}));
+
+ if setup_repositories && let Some(repositories) = composer_json.get("repositories").cloned() {
+ let packagist_false = serde_json::json!({ "packagist.org": false });
+ let already_present = match &repositories {
+ serde_json::Value::Object(map) => map.contains_key("packagist.org"),
+ serde_json::Value::Array(list) => list.iter().any(|r| r == &packagist_false),
+ _ => false,
+ };
+ if !already_present {
+ match composer_json
+ .get_mut("repositories")
+ .and_then(|r| r.as_array_mut())
+ {
+ Some(list) => list.push(packagist_false),
+ None => {
+ if let Some(map) = composer_json
+ .get_mut("repositories")
+ .and_then(|r| r.as_object_mut())
+ {
+ map.insert("packagist.org".to_string(), serde_json::Value::Bool(false));
+ }
+ }
+ }
+ }
+ }
+
+ std::env::set_current_dir(&dir).unwrap();
+ std::fs::write(
+ dir.join("composer.json"),
+ serde_json::to_string_pretty(&composer_json).unwrap(),
+ )
+ .unwrap();
+ std::fs::write(
+ dir.join("auth.json"),
+ serde_json::to_string_pretty(&auth_json).unwrap(),
+ )
+ .unwrap();
+ if let Some(composer_lock) = composer_lock {
+ std::fs::write(
+ dir.join("composer.lock"),
+ serde_json::to_string_pretty(composer_lock).unwrap(),
+ )
+ .unwrap();
+ }
+
+ TearDown {
+ temp_dir,
+ prev_cwd,
+ prev_composer_home,
+ }
+}
+
+/// ref: TestCase::getApplicationTester
+pub fn get_application_tester() -> ApplicationTester {
+ let application = ApplicationHandle::new("Composer".to_string(), "".to_string()).unwrap();
+ application.set_catch_exceptions(false);
+ ApplicationTester::new(application)
+}
+
+/// ref: Symfony\Component\Console\Tester\ApplicationTester::run options.
+///
+/// Lives in the test harness (not `shirabe_external_packages`) because the tester drives shirabe's
+/// own `ApplicationHandle`, which the external-packages crate cannot depend on.
+#[derive(Debug, Default)]
+pub struct RunOptions {
+ pub interactive: Option<bool>,
+ pub decorated: Option<bool>,
+ pub verbosity: Option<i64>,
+ pub capture_stderr_separately: bool,
+}
+
+/// ref: Symfony\Component\Console\Tester\ApplicationTester (with TesterTrait inlined).
+///
+/// The shared `TesterTrait` logic is inlined here; revisit common extraction when `CommandTester`
+/// is ported.
+pub struct ApplicationTester {
+ application: ApplicationHandle,
+ inputs: Vec<String>,
+ status_code: Option<i32>,
+ output: Option<Rc<RefCell<dyn OutputInterface>>>,
+ /// Handles retained before injection so `get_display`/`get_error_output` can read the memory
+ /// streams without relying on a `get_stream()` accessor across the ConsoleOutput composition gap.
+ output_stream: Option<PhpResource>,
+ error_stream: Option<PhpResource>,
+ capture_streams_independently: bool,
+}
+
+impl ApplicationTester {
+ pub fn new(application: ApplicationHandle) -> Self {
+ Self {
+ application,
+ inputs: Vec::new(),
+ status_code: None,
+ output: None,
+ output_stream: None,
+ error_stream: None,
+ capture_streams_independently: false,
+ }
+ }
+
+ pub fn set_inputs(&mut self, inputs: Vec<String>) -> &mut Self {
+ self.inputs = inputs;
+ self
+ }
+
+ pub fn run(
+ &mut self,
+ input: Vec<(PhpMixed, PhpMixed)>,
+ options: RunOptions,
+ ) -> anyhow::Result<i32> {
+ let mut array_input = ArrayInput::new(input, None)?;
+ if let Some(interactive) = options.interactive {
+ array_input.set_interactive(interactive);
+ }
+ if !self.inputs.is_empty() {
+ array_input.set_stream(Self::create_stream(&self.inputs));
+ }
+
+ self.init_output(&options);
+
+ let input: Rc<RefCell<dyn InputInterface>> = Rc::new(RefCell::new(array_input));
+ let output = self.output.clone().expect("init_output initializes output");
+
+ let status_code = self.application.run(Some(input), Some(output))?;
+ self.status_code = Some(status_code);
+
+ Ok(status_code)
+ }
+
+ fn init_output(&mut self, options: &RunOptions) {
+ self.capture_streams_independently = options.capture_stderr_separately;
+
+ if !self.capture_streams_independently {
+ let stream = shirabe_php_shim::php_fopen_resource("php://memory", "w");
+ self.output_stream = Some(stream.clone());
+ self.error_stream = None;
+
+ let output = StreamOutput::new(stream, None, None, None)
+ .unwrap()
+ .expect("php://memory is a valid stream");
+ if let Some(decorated) = options.decorated {
+ output.set_decorated(decorated);
+ }
+ if let Some(verbosity) = options.verbosity {
+ output.set_verbosity(verbosity);
+ }
+ self.output = Some(Rc::new(RefCell::new(output)));
+ } else {
+ let stdout = shirabe_php_shim::php_fopen_resource("php://memory", "w");
+ let stderr = shirabe_php_shim::php_fopen_resource("php://memory", "w");
+ self.output_stream = Some(stdout.clone());
+ self.error_stream = Some(stderr.clone());
+
+ let mut output =
+ ConsoleOutput::new(options.verbosity, options.decorated, None).unwrap();
+
+ let error_output = StreamOutput::new(stderr, None, None, None)
+ .unwrap()
+ .expect("php://memory is a valid stream");
+ error_output.set_formatter(output.get_formatter());
+ error_output.set_verbosity(output.get_verbosity());
+ error_output.set_decorated(output.is_decorated());
+
+ output.set_error_output(Rc::new(RefCell::new(error_output)));
+ output.__set_stream(stdout);
+
+ self.output = Some(Rc::new(RefCell::new(output)));
+ }
+ }
+
+ fn create_stream(inputs: &[String]) -> PhpResource {
+ let stream = shirabe_php_shim::php_fopen_resource("php://memory", "r+");
+ for input in inputs {
+ shirabe_php_shim::fwrite_resource(
+ &stream,
+ &format!("{}{}", input, shirabe_php_shim::PHP_EOL),
+ );
+ }
+ shirabe_php_shim::rewind(&stream);
+ stream
+ }
+
+ pub fn get_status_code(&self) -> i32 {
+ self.status_code
+ .expect("status code not initialized; did you run() before requesting it?")
+ }
+
+ pub fn get_display(&self) -> String {
+ let stream = self
+ .output_stream
+ .as_ref()
+ .expect("output not initialized; did you run() before requesting the display?");
+ shirabe_php_shim::rewind(stream);
+ shirabe_php_shim::stream_get_contents(stream).unwrap_or_default()
+ }
+
+ pub fn get_error_output(&self) -> String {
+ assert!(
+ self.capture_streams_independently,
+ "The error output is not available when the tester is run without \"capture_stderr_separately\" option set."
+ );
+ let stream = self
+ .error_stream
+ .as_ref()
+ .expect("error output not initialized; did you run() before requesting it?");
+ shirabe_php_shim::rewind(stream);
+ shirabe_php_shim::stream_get_contents(stream).unwrap_or_default()
+ }
+}