From 997387ee50e39cf2a87cd494c49c93f551d2d28c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dario=20Vladovi=C4=87?= Date: Wed, 29 Jul 2020 18:26:46 +0200 Subject: [PATCH] refactor(java): parse version using regex (#1496) * refactor(java): parse version using regex Mock java version retrieval & extend java module test suite with rendering tests. * chore: remove nom crate * fix(java): support parsing version from both stdout & stderr * fix(java): fix java command mock * refactor(java): simplify version regex --- Cargo.lock | 37 ---- Cargo.toml | 1 - src/modules/java.rs | 248 ++++++++++++++++------- src/modules/utils/java_version_parser.rs | 65 ------ src/modules/utils/mod.rs | 1 - src/utils.rs | 4 + 6 files changed, 179 insertions(+), 177 deletions(-) delete mode 100644 src/modules/utils/java_version_parser.rs diff --git a/Cargo.lock b/Cargo.lock index f3aa9d18..36caa95d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -503,19 +503,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" -[[package]] -name = "lexical-core" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616" -dependencies = [ - "arrayvec", - "bitflags", - "cfg-if", - "ryu", - "static_assertions", -] - [[package]] name = "libc" version = "0.2.74" @@ -646,17 +633,6 @@ dependencies = [ "libc", ] -[[package]] -name = "nom" -version = "5.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" -dependencies = [ - "lexical-core", - "memchr", - "version_check", -] - [[package]] name = "ntapi" version = "0.3.4" @@ -1118,7 +1094,6 @@ dependencies = [ "log", "native-tls", "nix 0.18.0", - "nom", "once_cell", "open", "os_info", @@ -1153,12 +1128,6 @@ dependencies = [ "syn", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.8.0" @@ -1370,12 +1339,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" -[[package]] -name = "version_check" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" - [[package]] name = "void" version = "1.0.2" diff --git a/Cargo.toml b/Cargo.toml index 009b5eb7..41bc3117 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,6 @@ starship_module_config_derive = { version = "0.1.0", path = "starship_module_con yaml-rust = "0.4" pest = "^2.1" pest_derive = "^2.1" -nom = "5.1.2" regex = "1.3.9" os_info = "2.0.7" urlencoding = "1.1.1" diff --git a/src/modules/java.rs b/src/modules/java.rs index faf7d8c6..4298108b 100644 --- a/src/modules/java.rs +++ b/src/modules/java.rs @@ -3,9 +3,11 @@ use crate::formatter::StringFormatter; use super::{Context, Module, RootModuleConfig}; -use crate::modules::utils::java_version_parser; use crate::utils; +use regex::Regex; +const JAVA_VERSION_PATTERN: &str = "(?P[\\d\\.]+)[^\\s]*\\s(?:built|from)"; + /// Creates a module with the current Java version /// /// Will display the Java version if any of the following criteria are met: @@ -22,39 +24,37 @@ pub fn module<'a>(context: &'a Context) -> Option> { return None; } - match get_java_version() { - Some(java_version) => { - let mut module = context.new_module("java"); - let config: JavaConfig = JavaConfig::try_load(module.config); - let formatted_version = format_java_version(java_version)?; + let java_version = get_java_version()?; - let parsed = StringFormatter::new(config.format).and_then(|formatter| { - formatter - .map_meta(|var, _| match var { - "symbol" => Some(config.symbol), - _ => None, - }) - .map_style(|variable| match variable { - "style" => Some(Ok(config.style)), - _ => None, - }) - .map(|variable| match variable { - "version" => Some(Ok(&formatted_version)), - _ => None, - }) - .parse(None) - }); - module.set_segments(match parsed { - Ok(segments) => segments, - Err(error) => { - log::warn!("Error in module `java`:\n{}", error); - return None; - } - }); - Some(module) + let mut module = context.new_module("java"); + let config: JavaConfig = JavaConfig::try_load(module.config); + + let parsed = StringFormatter::new(config.format).and_then(|formatter| { + formatter + .map_meta(|var, _| match var { + "symbol" => Some(config.symbol), + _ => None, + }) + .map_style(|variable| match variable { + "style" => Some(Ok(config.style)), + _ => None, + }) + .map(|variable| match variable { + "version" => Some(Ok(&java_version)), + _ => None, + }) + .parse(None) + }); + + module.set_segments(match parsed { + Ok(segments) => segments, + Err(error) => { + log::warn!("Error in module `java`:\n{}", error); + return None; } - None => None, - } + }); + + Some(module) } fn get_java_version() -> Option { @@ -64,79 +64,181 @@ fn get_java_version() -> Option { }; let output = utils::exec_cmd(&java_command.as_str(), &["-Xinternalversion"])?; - Some(format!("{}{}", output.stdout, output.stderr)) + let java_version = if output.stdout.is_empty() { + output.stderr + } else { + output.stdout + }; + + parse_java_version(&java_version) } -/// Extract the java version from `java_out`. -fn format_java_version(java_out: String) -> Option { - java_version_parser::parse_jre_version(&java_out).map(|result| format!("v{}", result)) +fn parse_java_version(java_version: &str) -> Option { + let re = Regex::new(JAVA_VERSION_PATTERN).ok()?; + let captures = re.captures(java_version)?; + let version = &captures["version"]; + + Some(format!("v{}", &version)) } #[cfg(test)] mod tests { use super::*; + use crate::modules::utils::test::render_module; + use ansi_term::Color; + use std::fs::File; + use std::io; #[test] - fn test_format_java_version_openjdk() { - let java_8 = String::from("OpenJDK 64-Bit Server VM (25.222-b10) for linux-amd64 JRE (1.8.0_222-b10), built on Jul 11 2019 10:18:43 by \"openjdk\" with gcc 4.4.7 20120313 (Red Hat 4.4.7-23)"); - let java_11 = String::from("OpenJDK 64-Bit Server VM (11.0.4+11-post-Ubuntu-1ubuntu219.04) for linux-amd64 JRE (11.0.4+11-post-Ubuntu-1ubuntu219.04), built on Jul 18 2019 18:21:46 by \"build\" with gcc 8.3.0"); - assert_eq!(format_java_version(java_11), Some(String::from("v11.0.4"))); - assert_eq!(format_java_version(java_8), Some(String::from("v1.8.0"))); + fn test_parse_java_version_openjdk() { + let java_8 = "OpenJDK 64-Bit Server VM (25.222-b10) for linux-amd64 JRE (1.8.0_222-b10), built on Jul 11 2019 10:18:43 by \"openjdk\" with gcc 4.4.7 20120313 (Red Hat 4.4.7-23)"; + let java_11 = "OpenJDK 64-Bit Server VM (11.0.4+11-post-Ubuntu-1ubuntu219.04) for linux-amd64 JRE (11.0.4+11-post-Ubuntu-1ubuntu219.04), built on Jul 18 2019 18:21:46 by \"build\" with gcc 8.3.0"; + assert_eq!(parse_java_version(java_11), Some("v11.0.4".to_string())); + assert_eq!(parse_java_version(java_8), Some("v1.8.0".to_string())); } #[test] - fn test_format_java_version_oracle() { - let java_8 = String::from("Java HotSpot(TM) Client VM (25.65-b01) for linux-arm-vfp-hflt JRE (1.8.0_65-b17), built on Oct 6 2015 16:19:04 by \"java_re\" with gcc 4.7.2 20120910 (prerelease)"); - assert_eq!(format_java_version(java_8), Some(String::from("v1.8.0"))); + fn test_parse_java_version_oracle() { + let java_8 = "Java HotSpot(TM) Client VM (25.65-b01) for linux-arm-vfp-hflt JRE (1.8.0_65-b17), built on Oct 6 2015 16:19:04 by \"java_re\" with gcc 4.7.2 20120910 (prerelease)"; + assert_eq!(parse_java_version(java_8), Some("v1.8.0".to_string())); } #[test] - fn test_format_java_version_redhat() { - let java_8 = String::from("OpenJDK 64-Bit Server VM (25.222-b10) for linux-amd64 JRE (1.8.0_222-b10), built on Jul 11 2019 20:48:53 by \"root\" with gcc 7.3.1 20180303 (Red Hat 7.3.1-5)"); - let java_12 = String::from("OpenJDK 64-Bit Server VM (12.0.2+10) for linux-amd64 JRE (12.0.2+10), built on Jul 18 2019 14:41:47 by \"jenkins\" with gcc 7.3.1 20180303 (Red Hat 7.3.1-5)"); - assert_eq!(format_java_version(java_8), Some(String::from("v1.8.0"))); - assert_eq!(format_java_version(java_12), Some(String::from("v12.0.2"))); + fn test_parse_java_version_redhat() { + let java_8 = "OpenJDK 64-Bit Server VM (25.222-b10) for linux-amd64 JRE (1.8.0_222-b10), built on Jul 11 2019 20:48:53 by \"root\" with gcc 7.3.1 20180303 (Red Hat 7.3.1-5)"; + let java_12 = "OpenJDK 64-Bit Server VM (12.0.2+10) for linux-amd64 JRE (12.0.2+10), built on Jul 18 2019 14:41:47 by \"jenkins\" with gcc 7.3.1 20180303 (Red Hat 7.3.1-5)"; + assert_eq!(parse_java_version(java_8), Some("v1.8.0".to_string())); + assert_eq!(parse_java_version(java_12), Some("v12.0.2".to_string())); } #[test] - fn test_format_java_version_zulu() { - let java_8 = String::from("OpenJDK 64-Bit Server VM (25.222-b10) for linux-amd64 JRE (Zulu 8.40.0.25-CA-linux64) (1.8.0_222-b10), built on Jul 11 2019 11:36:39 by \"zulu_re\" with gcc 4.4.7 20120313 (Red Hat 4.4.7-3)"); - let java_11 = String::from("OpenJDK 64-Bit Server VM (11.0.4+11-LTS) for linux-amd64 JRE (Zulu11.33+15-CA) (11.0.4+11-LTS), built on Jul 11 2019 21:37:17 by \"zulu_re\" with gcc 4.9.2 20150212 (Red Hat 4.9.2-6)"); - assert_eq!(format_java_version(java_8), Some(String::from("v1.8.0"))); - assert_eq!(format_java_version(java_11), Some(String::from("v11.0.4"))); + fn test_parse_java_version_zulu() { + let java_8 = "OpenJDK 64-Bit Server VM (25.222-b10) for linux-amd64 JRE (Zulu 8.40.0.25-CA-linux64) (1.8.0_222-b10), built on Jul 11 2019 11:36:39 by \"zulu_re\" with gcc 4.4.7 20120313 (Red Hat 4.4.7-3)"; + let java_11 = "OpenJDK 64-Bit Server VM (11.0.4+11-LTS) for linux-amd64 JRE (Zulu11.33+15-CA) (11.0.4+11-LTS), built on Jul 11 2019 21:37:17 by \"zulu_re\" with gcc 4.9.2 20150212 (Red Hat 4.9.2-6)"; + assert_eq!(parse_java_version(java_8), Some("v1.8.0".to_string())); + assert_eq!(parse_java_version(java_11), Some("v11.0.4".to_string())); } #[test] - fn test_format_java_version_eclipse_openj9() { - let java_8 = String::from("Eclipse OpenJ9 OpenJDK 64-bit Server VM (1.8.0_222-b10) from linux-amd64 JRE with Extensions for OpenJDK for Eclipse OpenJ9 8.0.222.0, built on Jul 17 2019 21:29:18 by jenkins with g++ (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5)"); - let java_11 = String::from("Eclipse OpenJ9 OpenJDK 64-bit Server VM (11.0.4+11) from linux-amd64 JRE with Extensions for OpenJDK for Eclipse OpenJ9 11.0.4.0, built on Jul 17 2019 21:51:37 by jenkins with g++ (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5)"); - assert_eq!(format_java_version(java_8), Some(String::from("v1.8.0"))); - assert_eq!(format_java_version(java_11), Some(String::from("v11.0.4"))); + fn test_parse_java_version_eclipse_openj9() { + let java_8 = "Eclipse OpenJ9 OpenJDK 64-bit Server VM (1.8.0_222-b10) from linux-amd64 JRE with Extensions for OpenJDK for Eclipse OpenJ9 8.0.222.0, built on Jul 17 2019 21:29:18 by jenkins with g++ (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5)"; + let java_11 = "Eclipse OpenJ9 OpenJDK 64-bit Server VM (11.0.4+11) from linux-amd64 JRE with Extensions for OpenJDK for Eclipse OpenJ9 11.0.4.0, built on Jul 17 2019 21:51:37 by jenkins with g++ (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5)"; + assert_eq!(parse_java_version(java_8), Some("v1.8.0".to_string())); + assert_eq!(parse_java_version(java_11), Some("v11.0.4".to_string())); } #[test] - fn test_format_java_version_graalvm() { - let java_8 = String::from("OpenJDK 64-Bit GraalVM CE 19.2.0.1 (25.222-b08-jvmci-19.2-b02) for linux-amd64 JRE (8u222), built on Jul 19 2019 17:37:13 by \"buildslave\" with gcc 7.3.0"); - assert_eq!(format_java_version(java_8), Some(String::from("v8"))); + fn test_parse_java_version_graalvm() { + let java_8 = "OpenJDK 64-Bit GraalVM CE 19.2.0.1 (25.222-b08-jvmci-19.2-b02) for linux-amd64 JRE (8u222), built on Jul 19 2019 17:37:13 by \"buildslave\" with gcc 7.3.0"; + assert_eq!(parse_java_version(java_8), Some("v8".to_string())); } #[test] - fn test_format_java_version_amazon_corretto() { - let java_8 = String::from("OpenJDK 64-Bit Server VM (25.222-b10) for linux-amd64 JRE (1.8.0_222-b10), built on Jul 11 2019 20:48:53 by \"root\" with gcc 7.3.1 20180303 (Red Hat 7.3.1-5)"); - let java_11 = String::from("OpenJDK 64-Bit Server VM (11.0.4+11-LTS) for linux-amd64 JRE (11.0.4+11-LTS), built on Jul 11 2019 20:06:11 by \"\" with gcc 7.3.1 20180303 (Red Hat 7.3.1-5)"); - assert_eq!(format_java_version(java_8), Some(String::from("v1.8.0"))); - assert_eq!(format_java_version(java_11), Some(String::from("v11.0.4"))); + fn test_parse_java_version_amazon_corretto() { + let java_8 = "OpenJDK 64-Bit Server VM (25.222-b10) for linux-amd64 JRE (1.8.0_222-b10), built on Jul 11 2019 20:48:53 by \"root\" with gcc 7.3.1 20180303 (Red Hat 7.3.1-5)"; + let java_11 = "OpenJDK 64-Bit Server VM (11.0.4+11-LTS) for linux-amd64 JRE (11.0.4+11-LTS), built on Jul 11 2019 20:06:11 by \"\" with gcc 7.3.1 20180303 (Red Hat 7.3.1-5)"; + assert_eq!(parse_java_version(java_8), Some("v1.8.0".to_string())); + assert_eq!(parse_java_version(java_11), Some("v11.0.4".to_string())); } #[test] - fn test_format_java_version_sapmachine() { - let java_11 = String::from("OpenJDK 64-Bit Server VM (11.0.4+11-LTS-sapmachine) for linux-amd64 JRE (11.0.4+11-LTS-sapmachine), built on Jul 17 2019 08:58:43 by \"\" with gcc 7.3.0"); - assert_eq!(format_java_version(java_11), Some(String::from("v11.0.4"))); + fn test_parse_java_version_sapmachine() { + let java_11 = "OpenJDK 64-Bit Server VM (11.0.4+11-LTS-sapmachine) for linux-amd64 JRE (11.0.4+11-LTS-sapmachine), built on Jul 17 2019 08:58:43 by \"\" with gcc 7.3.0"; + assert_eq!(parse_java_version(java_11), Some("v11.0.4".to_string())); } #[test] - fn test_format_java_version_unknown() { - let unknown_jre = String::from("Unknown JRE"); - assert_eq!(format_java_version(unknown_jre), None); + fn test_parse_java_version_unknown() { + let unknown_jre = "Unknown JRE"; + assert_eq!(parse_java_version(unknown_jre), None); + } + + #[test] + fn folder_without_java_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let actual = render_module("java", dir.path(), None); + let expected = None; + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_java_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("Main.java"))?.sync_all()?; + let actual = render_module("java", dir.path(), None); + let expected = Some(format!("via {} ", Color::Red.dimmed().paint("☕ v13.0.2"))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_class_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("Main.class"))?.sync_all()?; + let actual = render_module("java", dir.path(), None); + let expected = Some(format!("via {} ", Color::Red.dimmed().paint("☕ v13.0.2"))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_gradle_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("build.gradle"))?.sync_all()?; + let actual = render_module("java", dir.path(), None); + let expected = Some(format!("via {} ", Color::Red.dimmed().paint("☕ v13.0.2"))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_jar_archive() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("test.jar"))?.sync_all()?; + let actual = render_module("java", dir.path(), None); + let expected = Some(format!("via {} ", Color::Red.dimmed().paint("☕ v13.0.2"))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_pom_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("pom.xml"))?.sync_all()?; + let actual = render_module("java", dir.path(), None); + let expected = Some(format!("via {} ", Color::Red.dimmed().paint("☕ v13.0.2"))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_gradle_kotlin_build_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("build.gradle.kts"))?.sync_all()?; + let actual = render_module("java", dir.path(), None); + let expected = Some(format!("via {} ", Color::Red.dimmed().paint("☕ v13.0.2"))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_sbt_build_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("build.gradle.kts"))?.sync_all()?; + let actual = render_module("java", dir.path(), None); + let expected = Some(format!("via {} ", Color::Red.dimmed().paint("☕ v13.0.2"))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_java_version_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join(".java-version"))?.sync_all()?; + let actual = render_module("java", dir.path(), None); + let expected = Some(format!("via {} ", Color::Red.dimmed().paint("☕ v13.0.2"))); + assert_eq!(expected, actual); + dir.close() } } diff --git a/src/modules/utils/java_version_parser.rs b/src/modules/utils/java_version_parser.rs deleted file mode 100644 index cf9445df..00000000 --- a/src/modules/utils/java_version_parser.rs +++ /dev/null @@ -1,65 +0,0 @@ -use nom::{ - branch::alt, - bytes::complete::{tag, take_until, take_while1}, - combinator::rest, - sequence::{preceded, tuple}, - IResult, -}; - -fn is_version(c: char) -> bool { - c >= '0' && c <= '9' || c == '.' -} - -fn version(input: &str) -> IResult<&str, &str> { - take_while1(&is_version)(input) -} - -fn zulu(input: &str) -> IResult<&str, &str> { - let zulu_prefix_value = preceded(take_until("("), tag("(")); - preceded(zulu_prefix_value, version)(input) -} - -fn jre_prefix(input: &str) -> IResult<&str, &str> { - preceded(take_until("JRE ("), tag("JRE ("))(input) -} - -fn j9_prefix(input: &str) -> IResult<&str, &str> { - preceded(take_until("VM ("), tag("VM ("))(input) -} - -fn suffix(input: &str) -> IResult<&str, &str> { - rest(input) -} - -fn parse(input: &str) -> IResult<&str, &str> { - let prefix = alt((jre_prefix, j9_prefix)); - let version_or_zulu = alt((version, zulu)); - let (input, (_, version, _)) = tuple((prefix, version_or_zulu, suffix))(input)?; - - Ok((input, version)) -} - -/// Parse the java version from `java -Xinternalversion` format. -/// -/// The expected format is similar to: -/// "JRE (1.8.0_222-b10)" -/// "JRE (Zulu 8.40.0.25-CA-linux64) (1.8.0_222-b10)" -/// "VM (1.8.0_222-b10)". -/// -/// Some Java vendors might not follow this format. -pub fn parse_jre_version(input: &str) -> Option<&str> { - parse(input).map(|result| result.1).ok() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_eclipse_openj9() { - let java_8 = "Eclipse OpenJ9 OpenJDK 64-bit Server VM (1.8.0_222-b10) from linux-amd64 JRE with Extensions for OpenJDK for Eclipse OpenJ9 8.0.222.0, built on Jul 17 2019 21:29:18 by jenkins with g++ (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5)"; - let java_11 = "Eclipse OpenJ9 OpenJDK 64-bit Server VM (11.0.4+11) from linux-amd64 JRE with Extensions for OpenJDK for Eclipse OpenJ9 11.0.4.0, built on Jul 17 2019 21:51:37 by jenkins with g++ (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5)"; - assert_eq!(parse(java_8), Ok(("", "1.8.0"))); - assert_eq!(parse(java_11), Ok(("", "11.0.4"))); - } -} diff --git a/src/modules/utils/mod.rs b/src/modules/utils/mod.rs index d164de4d..89b12415 100644 --- a/src/modules/utils/mod.rs +++ b/src/modules/utils/mod.rs @@ -1,5 +1,4 @@ pub mod directory; -pub mod java_version_parser; #[cfg(target_os = "windows")] pub mod directory_win; diff --git a/src/utils.rs b/src/utils.rs index 34fafce6..75eb478e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -80,6 +80,10 @@ Elixir 1.10 (compiled with Erlang/OTP 22)\n", stdout: String::from("v3.1.1+gafe7058\n"), stderr: String::default(), }), + s if s.ends_with("java -Xinternalversion") => Some(CommandOutput { + stdout: String::from("OpenJDK 64-Bit Server VM (13.0.2+8) for bsd-amd64 JRE (13.0.2+8), built on Feb 6 2020 02:07:52 by \"brew\" with clang 4.2.1 Compatible Apple LLVM 11.0.0 (clang-1100.0.33.17)"), + stderr: String::default(), + }), "julia --version" => Some(CommandOutput { stdout: String::from("julia version 1.4.0\n"), stderr: String::default(),