aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-rpc/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe-php-rpc/src/lib.rs')
-rw-r--r--crates/shirabe-php-rpc/src/lib.rs522
1 files changed, 392 insertions, 130 deletions
diff --git a/crates/shirabe-php-rpc/src/lib.rs b/crates/shirabe-php-rpc/src/lib.rs
index 69790695..e2f5fd1e 100644
--- a/crates/shirabe-php-rpc/src/lib.rs
+++ b/crates/shirabe-php-rpc/src/lib.rs
@@ -30,49 +30,11 @@ pub fn get_php_binary() -> String {
}
}
-/// PHP `defined($name)`.
-pub fn has_constant(name: &str) -> bool {
- match call("defined", name) {
- PhpMixed::Bool(b) => b,
- other => panic!("PHP RPC: `defined` did not return a bool: {other:?}"),
- }
-}
-
/// PHP `constant($name)`.
-pub fn get_constant(name: &str) -> PhpMixed {
+fn get_constant(name: &str) -> PhpMixed {
call("constant", name)
}
-/// PHP `class_exists($name)`, with autoloading, as the runtime sees it.
-pub fn class_exists(name: &str) -> bool {
- match call("class_exists", name) {
- PhpMixed::Bool(exists) => exists,
- other => panic!("PHP RPC: `class_exists` returned an unexpected value: {other:?}"),
- }
-}
-
-/// PHP `inet_pton($address)`.
-pub fn inet_pton(address: &str) -> PhpMixed {
- call("inet_pton", address)
-}
-
-/// PHP `curl_version()['version']`.
-pub fn curl_version() -> Option<String> {
- match call("curl_version", "") {
- PhpMixed::String(s) => Some(s),
- PhpMixed::Null => None,
- other => panic!("PHP RPC: `curl_version` returned an unexpected value: {other:?}"),
- }
-}
-
-/// PHP `(new \ReflectionExtension($name))->info()` output.
-pub fn get_extension_info(name: &str) -> String {
- match call("extension_info", name) {
- PhpMixed::String(s) => s,
- other => panic!("PHP RPC: `extension_info` did not return a string: {other:?}"),
- }
-}
-
/// `curl_version()`, together with the `CURL_*` constants the `diagnose` command consults. Every
/// `Option` field is `None` when the corresponding array key or constant is absent.
#[derive(Debug)]
@@ -89,7 +51,7 @@ pub struct Curl {
}
/// Everything the `diagnose` command needs to know about the PHP runtime, fetched in a single
-/// round trip because the command would otherwise probe the same runtime dozens of times.
+/// round trip because the command would otherwise query the same runtime dozens of times.
#[derive(Debug)]
pub struct Diagnostics {
pub php_version: String,
@@ -118,28 +80,28 @@ pub struct Diagnostics {
}
impl Diagnostics {
- /// PHP `extension_loaded($name)`. Only the extensions the worker probes can be asked about;
+ /// PHP `extension_loaded($name)`. Only the extensions the worker reports can be asked about;
/// any other name is a bug in the caller, not a missing extension.
pub fn extension_loaded(&self, name: &str) -> bool {
*self.extensions.get(name).unwrap_or_else(|| {
- panic!("PHP RPC: extension `{name}` is not probed by the diagnose payload")
+ panic!("PHP RPC: extension `{name}` is not reported by the diagnose payload")
})
}
- /// PHP `function_exists($name)`. See [`Diagnostics::extension_loaded`] for the fixed probe set.
+ /// PHP `function_exists($name)`. See [`Diagnostics::extension_loaded`] for the fixed set of names.
pub fn function_exists(&self, name: &str) -> bool {
*self.functions.get(name).unwrap_or_else(|| {
- panic!("PHP RPC: function `{name}` is not probed by the diagnose payload")
+ panic!("PHP RPC: function `{name}` is not reported by the diagnose payload")
})
}
/// PHP `ini_get($option)`, with PHP's `false` (no such setting) mapped to `None`. See
- /// [`Diagnostics::extension_loaded`] for the fixed probe set.
+ /// [`Diagnostics::extension_loaded`] for the fixed set of names.
pub fn ini_get(&self, option: &str) -> Option<&str> {
self.ini_settings
.get(option)
.unwrap_or_else(|| {
- panic!("PHP RPC: ini setting `{option}` is not probed by the diagnose payload")
+ panic!("PHP RPC: ini setting `{option}` is not reported by the diagnose payload")
})
.as_deref()
}
@@ -155,123 +117,416 @@ pub fn get_diagnostics() -> &'static Diagnostics {
let payload = payload
.as_array()
.unwrap_or_else(|| panic!("PHP RPC: `diagnose` did not return an array: {payload:?}"));
+ let payload = &Payload {
+ what: "diagnose",
+ map: payload,
+ };
Diagnostics {
- php_version: string_field(payload, "php_version"),
- php_version_id: int_field(payload, "php_version_id"),
- php_binary: nullable_string_field(payload, "php_binary"),
- openssl_version_text: nullable_string_field(payload, "openssl_version_text"),
- openssl_version_number: int_field(payload, "openssl_version_number"),
- has_hhvm_version: bool_field(payload, "has_hhvm_version"),
- has_php_windows_version_build: bool_field(payload, "has_php_windows_version_build"),
- xdebug_active: bool_field(payload, "xdebug_active"),
- ioncube_loader_iversion: int_field(payload, "ioncube_loader_iversion"),
- ioncube_loader_version: string_field(payload, "ioncube_loader_version"),
- phpinfo_general: string_field(payload, "phpinfo_general"),
+ php_version: payload.string("php_version"),
+ php_version_id: payload.int("php_version_id"),
+ php_binary: payload.nullable_string("php_binary"),
+ openssl_version_text: payload.nullable_string("openssl_version_text"),
+ openssl_version_number: payload.int("openssl_version_number"),
+ has_hhvm_version: payload.bool("has_hhvm_version"),
+ has_php_windows_version_build: payload.bool("has_php_windows_version_build"),
+ xdebug_active: payload.bool("xdebug_active"),
+ ioncube_loader_iversion: payload.int("ioncube_loader_iversion"),
+ ioncube_loader_version: payload.string("ioncube_loader_version"),
+ phpinfo_general: payload.string("phpinfo_general"),
curl: curl_field(payload, "curl"),
- extensions: map_field(payload, "extensions")
+ extensions: payload.bool_map("extensions"),
+ functions: payload.bool_map("functions"),
+ ini_settings: payload
+ .entries("ini")
+ .map(|(name, value)| (name.clone(), as_nullable_string(value, "ini", name)))
+ .collect(),
+ }
+ })
+}
+
+/// The constants the `platform` payload carries, in the worker's order.
+const PLATFORM_CONSTANTS: &[&str] = &[
+ "PHP_VERSION",
+ "PHP_DEBUG",
+ "PHP_ZTS",
+ "PHP_INT_SIZE",
+ "AF_INET6",
+ "GD_VERSION",
+ "GMP_VERSION",
+ "ICONV_VERSION",
+ "INTL_ICU_VERSION",
+ "LIBXML_DOTTED_VERSION",
+ "MB_ONIGURUMA_VERSION",
+ "OPENSSL_VERSION_TEXT",
+ "PCRE_VERSION",
+ "PGSQL_LIBPQ_VERSION",
+ "RD_KAFKA_VERSION",
+ "SODIUM_LIBRARY_VERSION",
+ "LIBXSLT_DOTTED_VERSION",
+ "ZipArchive::LIBZIP_VERSION",
+ "ZLIB_VERSION",
+];
+
+/// The classes the `platform` payload carries the existence of.
+const PLATFORM_CLASSES: &[&str] = &["ResourceBundle", "IntlChar"];
+
+/// Everything `PlatformRepository` needs to know about the PHP runtime, fetched in a single round
+/// trip because it would otherwise query the same runtime once per extension and constant.
+///
+/// The fields standing in for PHP objects (`resource_bundle`, `imagick`) carry the entries the
+/// consumer reads off them rather than the object itself.
+#[derive(Debug, Clone)]
+pub struct PlatformInfo {
+ extensions: Vec<String>,
+ extension_versions: IndexMap<String, String>,
+ extension_info: IndexMap<String, String>,
+ /// Keyed as `ltrim($class.'::'.$constant, ':')`; `None` for a reported but undefined constant.
+ constants: IndexMap<String, Option<PhpMixed>>,
+ classes: IndexMap<String, bool>,
+ /// `curl_version()`, or null when the curl extension is not loaded.
+ pub curl_version: PhpMixed,
+ /// `@inet_pton('::')`.
+ pub inet_pton_ipv6: PhpMixed,
+ /// `['Version' => ResourceBundle::create('root', 'ICUDATA', false)->get('Version')]`, or null
+ /// when the class is absent or the bundle cannot be opened.
+ pub resource_bundle: PhpMixed,
+ /// `IntlChar::getUnicodeVersion()`, or null when the class is absent.
+ pub intl_char_unicode_version: PhpMixed,
+ /// `(new Imagick())->getVersion()`, or null when the extension is not loaded.
+ pub imagick: PhpMixed,
+}
+
+impl Default for PlatformInfo {
+ /// A runtime with no extensions loaded, no classes defined and every reported constant
+ /// undefined.
+ fn default() -> Self {
+ PlatformInfo {
+ extensions: Vec::new(),
+ extension_versions: IndexMap::new(),
+ extension_info: IndexMap::new(),
+ constants: PLATFORM_CONSTANTS
.iter()
- .map(|(name, value)| (name.clone(), as_bool(value, name)))
+ .map(|name| ((*name).to_string(), None))
.collect(),
- functions: map_field(payload, "functions")
+ classes: PLATFORM_CLASSES
.iter()
- .map(|(name, value)| (name.clone(), as_bool(value, name)))
+ .map(|name| ((*name).to_string(), false))
.collect(),
- ini_settings: map_field(payload, "ini")
+ curl_version: PhpMixed::Null,
+ inet_pton_ipv6: PhpMixed::Null,
+ resource_bundle: PhpMixed::Null,
+ intl_char_unicode_version: PhpMixed::Null,
+ imagick: PhpMixed::Null,
+ }
+ }
+}
+
+impl PlatformInfo {
+ /// PHP `get_loaded_extensions()`.
+ pub fn get_extensions(&self) -> &[String] {
+ &self.extensions
+ }
+
+ /// PHP `phpversion($extension)`, with PHP's `false` mapped to `'0'`.
+ pub fn get_extension_version(&self, extension: &str) -> &str {
+ self.extension_versions
+ .get(extension)
+ .unwrap_or_else(|| {
+ panic!("PHP RPC: extension `{extension}` is not loaded in the platform payload")
+ })
+ .as_str()
+ }
+
+ /// PHP `(new \ReflectionExtension($extension))->info()` output. Only the extensions the worker
+ /// reports can be asked about; any other name is a bug in the caller.
+ pub fn get_extension_info(&self, extension: &str) -> &str {
+ self.extension_info
+ .get(extension)
+ .unwrap_or_else(|| {
+ panic!("PHP RPC: extension `{extension}` info is not in the platform payload")
+ })
+ .as_str()
+ }
+
+ /// PHP `defined(ltrim($class.'::'.$constant, ':'))`. Only the constants the worker reports can
+ /// be asked about; any other name is a bug in the caller, not an undefined constant.
+ pub fn has_constant(&self, constant_name: &str, class: Option<&str>) -> bool {
+ self.constant(constant_name, class).is_some()
+ }
+
+ /// PHP `constant(ltrim($class.'::'.$constant, ':'))`, answering null for an undefined
+ /// constant. See [`PlatformInfo::has_constant`] for the fixed set of names.
+ pub fn get_constant(&self, constant_name: &str, class: Option<&str>) -> PhpMixed {
+ self.constant(constant_name, class)
+ .cloned()
+ .unwrap_or(PhpMixed::Null)
+ }
+
+ /// PHP `class_exists($class, false)`. See [`PlatformInfo::has_constant`] for the fixed set of
+ /// names.
+ pub fn has_class(&self, class: &str) -> bool {
+ *self.classes.get(class).unwrap_or_else(|| {
+ panic!("PHP RPC: class `{class}` is not reported by the platform payload")
+ })
+ }
+
+ /// For testing only: reports `extensions` as loaded, each at `version`.
+ pub fn __set_extensions(&mut self, extensions: Vec<String>, version: &str) {
+ self.extension_versions = extensions
+ .iter()
+ .map(|name| (name.clone(), version.to_string()))
+ .collect();
+ self.extensions = extensions;
+ }
+
+ /// For testing only: reports `info` as the `ReflectionExtension::info()` output of `extension`.
+ pub fn __set_extension_info(&mut self, extension: &str, info: &str) {
+ self.extension_info
+ .insert(extension.to_string(), info.to_string());
+ }
+
+ /// For testing only: reports the constant as defined with `value`. Panics on a constant the
+ /// worker does not report, so a test cannot describe a runtime the worker cannot report.
+ pub fn __set_constant(&mut self, constant_name: &str, class: Option<&str>, value: PhpMixed) {
+ let key = Self::constant_key(constant_name, class);
+ let entry = self.constants.get_mut(&key).unwrap_or_else(|| {
+ panic!("PHP RPC: constant `{key}` is not reported by the platform payload")
+ });
+ *entry = Some(value);
+ }
+
+ /// For testing only: reports the class as defined. See [`PlatformInfo::__set_constant`].
+ pub fn __set_class(&mut self, class: &str) {
+ let entry = self.classes.get_mut(class).unwrap_or_else(|| {
+ panic!("PHP RPC: class `{class}` is not reported by the platform payload")
+ });
+ *entry = true;
+ }
+
+ fn constant(&self, constant_name: &str, class: Option<&str>) -> Option<&PhpMixed> {
+ let key = Self::constant_key(constant_name, class);
+ self.constants
+ .get(&key)
+ .unwrap_or_else(|| {
+ panic!("PHP RPC: constant `{key}` is not reported by the platform payload")
+ })
+ .as_ref()
+ }
+
+ fn constant_key(constant_name: &str, class: Option<&str>) -> String {
+ match class {
+ Some(class) => format!("{class}::{constant_name}"),
+ None => constant_name.to_string(),
+ }
+ }
+}
+
+static PLATFORM_INFO: OnceLock<PlatformInfo> = OnceLock::new();
+
+/// PHP runtime information for `PlatformRepository`. The worker is queried once per process;
+/// subsequent calls reuse the cached payload.
+pub fn get_platform_info() -> &'static PlatformInfo {
+ PLATFORM_INFO.get_or_init(|| {
+ let payload = call("platform", "");
+ let payload = payload
+ .as_array()
+ .unwrap_or_else(|| panic!("PHP RPC: `platform` did not return an array: {payload:?}"));
+ let payload = &Payload {
+ what: "platform",
+ map: payload,
+ };
+
+ let constant_names = payload.string_list("constant_names");
+ assert_eq!(
+ constant_names
.iter()
- .map(|(name, value)| (name.clone(), as_nullable_string(value, name)))
+ .map(String::as_str)
+ .collect::<Vec<_>>(),
+ PLATFORM_CONSTANTS,
+ "PHP RPC: the worker reports different constants than the platform payload declares"
+ );
+ let constants = payload.map("constants");
+ let classes = payload.bool_map("classes");
+ assert_eq!(
+ classes.keys().map(String::as_str).collect::<Vec<_>>(),
+ PLATFORM_CLASSES,
+ "PHP RPC: the worker reports different classes than the platform payload declares"
+ );
+
+ PlatformInfo {
+ extensions: payload.string_list("extensions"),
+ extension_versions: payload.string_map("extension_versions"),
+ extension_info: payload.string_map("extension_info"),
+ constants: constant_names
+ .into_iter()
+ .map(|name| {
+ let value = constants.and_then(|map| map.get(&name)).cloned();
+ (name, value)
+ })
.collect(),
+ classes,
+ curl_version: payload.mixed("curl_version"),
+ inet_pton_ipv6: payload.mixed("inet_pton_ipv6"),
+ resource_bundle: payload.mixed("resource_bundle"),
+ intl_char_unicode_version: payload.mixed("intl_char_unicode_version"),
+ imagick: payload.mixed("imagick"),
}
})
}
-fn field<'a>(payload: &'a IndexMap<String, PhpMixed>, key: &str) -> &'a PhpMixed {
- payload
- .get(key)
- .unwrap_or_else(|| panic!("PHP RPC: `diagnose` payload has no `{key}` entry"))
+/// One worker answer, named after the dispatch entry that produced it so a decoding failure says
+/// which payload was malformed.
+struct Payload<'a> {
+ what: &'static str,
+ map: &'a IndexMap<String, PhpMixed>,
}
-fn string_field(payload: &IndexMap<String, PhpMixed>, key: &str) -> String {
- match field(payload, key) {
- PhpMixed::String(s) => s.clone(),
- other => panic!("PHP RPC: `diagnose` payload entry `{key}` is not a string: {other:?}"),
+impl Payload<'_> {
+ fn field(&self, key: &str) -> &PhpMixed {
+ self.map
+ .get(key)
+ .unwrap_or_else(|| panic!("PHP RPC: `{}` payload has no `{key}` entry", self.what))
}
-}
-fn nullable_string_field(payload: &IndexMap<String, PhpMixed>, key: &str) -> Option<String> {
- as_nullable_string(field(payload, key), key)
-}
+ fn mixed(&self, key: &str) -> PhpMixed {
+ self.field(key).clone()
+ }
-fn int_field(payload: &IndexMap<String, PhpMixed>, key: &str) -> i64 {
- match field(payload, key) {
- PhpMixed::Int(n) => *n,
- other => panic!("PHP RPC: `diagnose` payload entry `{key}` is not an int: {other:?}"),
+ fn string(&self, key: &str) -> String {
+ match self.field(key) {
+ PhpMixed::String(s) => s.clone(),
+ other => panic!(
+ "PHP RPC: `{}` payload entry `{key}` is not a string: {other:?}",
+ self.what
+ ),
+ }
}
-}
-fn bool_field(payload: &IndexMap<String, PhpMixed>, key: &str) -> bool {
- as_bool(field(payload, key), key)
-}
+ fn nullable_string(&self, key: &str) -> Option<String> {
+ as_nullable_string(self.field(key), self.what, key)
+ }
-fn map_field<'a>(
- payload: &'a IndexMap<String, PhpMixed>,
- key: &str,
-) -> &'a IndexMap<String, PhpMixed> {
- match field(payload, key) {
- PhpMixed::Array(map) => map,
- other => panic!("PHP RPC: `diagnose` payload entry `{key}` is not an array: {other:?}"),
+ fn int(&self, key: &str) -> i64 {
+ match self.field(key) {
+ PhpMixed::Int(n) => *n,
+ other => panic!(
+ "PHP RPC: `{}` payload entry `{key}` is not an int: {other:?}",
+ self.what
+ ),
+ }
+ }
+
+ fn bool(&self, key: &str) -> bool {
+ as_bool(self.field(key), self.what, key)
+ }
+
+ /// `None` for PHP's empty array, which carries no key type and so decodes as an empty list.
+ fn map(&self, key: &str) -> Option<&IndexMap<String, PhpMixed>> {
+ match self.field(key) {
+ PhpMixed::Array(map) => Some(map),
+ PhpMixed::List(items) if items.is_empty() => None,
+ other => panic!(
+ "PHP RPC: `{}` payload entry `{key}` is not an array: {other:?}",
+ self.what
+ ),
+ }
+ }
+
+ fn entries(&self, key: &str) -> impl Iterator<Item = (&String, &PhpMixed)> {
+ self.map(key).into_iter().flatten()
+ }
+
+ fn bool_map(&self, key: &str) -> IndexMap<String, bool> {
+ self.entries(key)
+ .map(|(name, value)| (name.clone(), as_bool(value, self.what, name)))
+ .collect()
+ }
+
+ fn string_map(&self, key: &str) -> IndexMap<String, String> {
+ self.entries(key)
+ .map(|(name, value)| match value {
+ PhpMixed::String(s) => (name.clone(), s.clone()),
+ other => panic!(
+ "PHP RPC: `{}` payload entry `{key}[{name}]` is not a string: {other:?}",
+ self.what
+ ),
+ })
+ .collect()
+ }
+
+ fn string_list(&self, key: &str) -> Vec<String> {
+ match self.field(key) {
+ PhpMixed::List(items) => items
+ .iter()
+ .map(|item| match item {
+ PhpMixed::String(s) => s.clone(),
+ other => panic!(
+ "PHP RPC: `{}` payload entry `{key}` has a non-string element: {other:?}",
+ self.what
+ ),
+ })
+ .collect(),
+ other => panic!(
+ "PHP RPC: `{}` payload entry `{key}` is not a list: {other:?}",
+ self.what
+ ),
+ }
}
}
-fn as_bool(value: &PhpMixed, key: &str) -> bool {
+fn as_bool(value: &PhpMixed, what: &str, key: &str) -> bool {
match value {
PhpMixed::Bool(b) => *b,
- other => panic!("PHP RPC: `diagnose` payload entry `{key}` is not a bool: {other:?}"),
+ other => panic!("PHP RPC: `{what}` payload entry `{key}` is not a bool: {other:?}"),
+ }
+}
+
+fn as_nullable_string(value: &PhpMixed, what: &str, key: &str) -> Option<String> {
+ match value {
+ PhpMixed::String(s) => Some(s.clone()),
+ PhpMixed::Null => None,
+ other => {
+ panic!("PHP RPC: `{what}` payload entry `{key}` is not a string or null: {other:?}")
+ }
}
}
-fn nullable_int_field(payload: &IndexMap<String, PhpMixed>, key: &str) -> Option<i64> {
- match field(payload, key) {
+fn nullable_int_field(payload: &Payload, key: &str) -> Option<i64> {
+ let what = payload.what;
+ match payload.field(key) {
PhpMixed::Int(n) => Some(*n),
PhpMixed::Null => None,
other => {
- panic!("PHP RPC: `diagnose` payload entry `{key}` is not an int or null: {other:?}")
+ panic!("PHP RPC: `{what}` payload entry `{key}` is not an int or null: {other:?}")
}
}
}
-fn curl_field(payload: &IndexMap<String, PhpMixed>, key: &str) -> Option<Curl> {
- let curl = match field(payload, key) {
+fn curl_field(payload: &Payload, key: &str) -> Option<Curl> {
+ let what = payload.what;
+ let curl = match payload.field(key) {
PhpMixed::Null => return None,
PhpMixed::Array(map) => map,
other => {
- panic!("PHP RPC: `diagnose` payload entry `{key}` is not an array or null: {other:?}")
+ panic!("PHP RPC: `{what}` payload entry `{key}` is not an array or null: {other:?}")
}
};
+ let curl = &Payload { what, map: curl };
Some(Curl {
- version: string_field(curl, "version"),
- libz_version: nullable_string_field(curl, "libz_version"),
- brotli_version: nullable_string_field(curl, "brotli_version"),
- ssl_version: nullable_string_field(curl, "ssl_version"),
+ version: curl.string("version"),
+ libz_version: curl.nullable_string("libz_version"),
+ brotli_version: curl.nullable_string("brotli_version"),
+ ssl_version: curl.nullable_string("ssl_version"),
features: nullable_int_field(curl, "features"),
version_zstd: nullable_int_field(curl, "version_zstd"),
version_http2: nullable_int_field(curl, "version_http2"),
- has_http_version_2_0: bool_field(curl, "has_http_version_2_0"),
+ has_http_version_2_0: curl.bool("has_http_version_2_0"),
version_http3: nullable_int_field(curl, "version_http3"),
})
}
-fn as_nullable_string(value: &PhpMixed, key: &str) -> Option<String> {
- match value {
- PhpMixed::String(s) => Some(s.clone()),
- PhpMixed::Null => None,
- other => {
- panic!("PHP RPC: `diagnose` payload entry `{key}` is not a string or null: {other:?}")
- }
- }
-}
-
/// PHP `phpversion($extension)`.
pub fn phpversion(extension: &str) -> Option<String> {
match call("phpversion", extension) {
@@ -281,11 +536,6 @@ pub fn phpversion(extension: &str) -> Option<String> {
}
}
-/// PHP `get_loaded_extensions()`.
-pub fn get_loaded_extensions() -> Vec<String> {
- string_list(call("get_loaded_extensions", ""), "get_loaded_extensions")
-}
-
/// `Composer\XdebugHandler\XdebugHandler::getAllIniFiles()` (minus the `self::$name` branch,
/// which is unreachable since this port never constructs an XdebugHandler): `[(string)
/// php_ini_loaded_file()]` merged with the trimmed, comma-split `php_ini_scanned_files()` list
@@ -851,12 +1101,6 @@ mod tests {
return;
}
- let extensions = get_loaded_extensions();
- assert!(
- extensions.iter().any(|extension| extension == "Core"),
- "expected the Core extension among {extensions:?}",
- );
-
// XdebugHandler::getAllIniFiles() always yields at least one entry, which is the empty
// string when no php.ini is loaded.
assert!(!get_all_ini_files().is_empty());
@@ -910,20 +1154,38 @@ mod tests {
}
#[test]
- fn queries_constants_when_php_available() {
+ fn queries_platform_info_when_php_available() {
if PhpExecutableFinder::new().find(false).is_none() {
// No PHP in this environment; the worker cannot start.
return;
}
- assert!(has_constant("PHP_VERSION"));
- assert!(!has_constant("SHIRABE_DOES_NOT_EXIST_XYZ"));
+ let platform_info = get_platform_info();
- assert_eq!(get_constant("PHP_INT_SIZE"), PhpMixed::Int(8));
- assert_eq!(get_constant("SHIRABE_DOES_NOT_EXIST_XYZ"), PhpMixed::Null);
- match get_constant("PHP_VERSION") {
+ let extensions = platform_info.get_extensions();
+ assert!(
+ extensions.iter().any(|extension| extension == "Core"),
+ "expected the Core extension among {extensions:?}",
+ );
+ assert_eq!(
+ platform_info.get_extension_version("Core"),
+ get_php_version()
+ );
+
+ assert!(platform_info.has_constant("PHP_VERSION", None));
+ assert_eq!(
+ platform_info.get_constant("PHP_INT_SIZE", None),
+ PhpMixed::Int(8)
+ );
+ match platform_info.get_constant("PHP_VERSION", None) {
PhpMixed::String(s) => assert!(!s.is_empty(), "expected a non-empty PHP_VERSION"),
other => panic!("expected a string, got {other:?}"),
}
}
+
+ #[test]
+ #[should_panic(expected = "is not reported by the platform payload")]
+ fn platform_info_rejects_an_unreported_constant() {
+ PlatformInfo::default().has_constant("SHIRABE_DOES_NOT_EXIST_XYZ", None);
+ }
}