|  | 
| 2 | 2 | // Licensed under the Apache License, Version 2.0 (see LICENSE). | 
| 3 | 3 | 
 | 
| 4 | 4 | use std::env; | 
|  | 5 | +use std::ffi::OsString; | 
| 5 | 6 | use std::io::Write; | 
| 6 | 7 | use std::path::{Path, PathBuf}; | 
| 7 | 8 | use std::process::{Command, Output, Stdio}; | 
| 8 | 9 | 
 | 
| 9 | 10 | use anyhow::{Context, Result}; | 
|  | 11 | +use regex::Regex; | 
| 10 | 12 | use tempfile::TempDir; | 
| 11 | 13 | use termcolor::{Color, WriteColor}; | 
| 12 | 14 | 
 | 
| @@ -130,6 +132,9 @@ pub(crate) fn run_integration_tests( | 
| 130 | 132 |         test_caching_issue_129(scie_pants_scie); | 
| 131 | 133 |         test_custom_pants_toml_issue_153(scie_pants_scie); | 
| 132 | 134 |         test_pants_native_client_perms_issue_182(scie_pants_scie); | 
|  | 135 | + | 
|  | 136 | +        #[cfg(unix)] | 
|  | 137 | +        test_non_utf8_env_vars_issue_198(scie_pants_scie); | 
| 133 | 138 |     } | 
| 134 | 139 | 
 | 
| 135 | 140 |     // Max Python supported is 3.8 and only Linux and macOS x86_64 wheels were released. | 
| @@ -888,3 +893,81 @@ fn test_pants_native_client_perms_issue_182(scie_pants_scie: &Path) { | 
| 888 | 893 |         decode_output(output.unwrap().stdout).unwrap().trim() | 
| 889 | 894 |     ); | 
| 890 | 895 | } | 
|  | 896 | + | 
|  | 897 | +#[cfg(unix)] | 
|  | 898 | +fn test_non_utf8_env_vars_issue_198(scie_pants_scie: &Path) { | 
|  | 899 | +    integration_test!( | 
|  | 900 | +        "Verifying scie-pants is robust to environments with non-utf8 env vars present ({issue})", | 
|  | 901 | +        issue = issue_link(198) | 
|  | 902 | +    ); | 
|  | 903 | + | 
|  | 904 | +    let tmpdir = create_tempdir().unwrap(); | 
|  | 905 | + | 
|  | 906 | +    let pants_release = "2.17.0a1"; | 
|  | 907 | +    let pants_toml_content = format!( | 
|  | 908 | +        r#" | 
|  | 909 | +        [GLOBAL] | 
|  | 910 | +        pants_version = "{pants_release}" | 
|  | 911 | +        "# | 
|  | 912 | +    ); | 
|  | 913 | +    let pants_toml = tmpdir.path().join("pants.toml"); | 
|  | 914 | +    write_file(&pants_toml, false, pants_toml_content).unwrap(); | 
|  | 915 | + | 
|  | 916 | +    use std::os::unix::ffi::OsStringExt; | 
|  | 917 | +    env::set_var("FOO", OsString::from_vec(vec![b'B', 0xa5, b'R'])); | 
|  | 918 | + | 
|  | 919 | +    let err = execute( | 
|  | 920 | +        Command::new(scie_pants_scie) | 
|  | 921 | +            .arg("-V") | 
|  | 922 | +            .env("RUST_LOG", "trace") | 
|  | 923 | +            .stderr(Stdio::piped()) | 
|  | 924 | +            .current_dir(&tmpdir), | 
|  | 925 | +    ) | 
|  | 926 | +    .unwrap_err(); | 
|  | 927 | +    let error_text = err.to_string(); | 
|  | 928 | +    // N.B.: This is a very hacky way to confirm the `scie-jump` is done processing env vars and has | 
|  | 929 | +    // exec'd the `scie-pants` native client; which then proceeds to choke on env vars in the same | 
|  | 930 | +    // way scie-jump <= 0.11.0 did using `env::vars()`. | 
|  | 931 | +    assert!(Regex::new(concat!( | 
|  | 932 | +        r#"exe: ".*/bindings/venvs/2\.17\.0a1/lib/python3\.9/"#, | 
|  | 933 | +        r#"site-packages/pants/bin/native_client""# | 
|  | 934 | +    )) | 
|  | 935 | +    .unwrap() | 
|  | 936 | +    .find(&error_text) | 
|  | 937 | +    .is_some()); | 
|  | 938 | +    assert!(error_text.contains("[DEBUG TimerFinished] jump::prepare_boot(), Elapsed=")); | 
|  | 939 | +    assert!(error_text | 
|  | 940 | +        .contains(r#"panicked at 'called `Result::unwrap()` on an `Err` value: "B\xA5R"'"#)); | 
|  | 941 | + | 
|  | 942 | +    // The error path we test below requires flowing through the pantsd path via PyNailgunClient. | 
|  | 943 | +    let err = execute( | 
|  | 944 | +        Command::new(scie_pants_scie) | 
|  | 945 | +            .arg("--pantsd") | 
|  | 946 | +            .arg("-V") | 
|  | 947 | +            .env("PANTS_NO_NATIVE_CLIENT", "1") | 
|  | 948 | +            .stderr(Stdio::piped()) | 
|  | 949 | +            .current_dir(&tmpdir), | 
|  | 950 | +    ) | 
|  | 951 | +    .unwrap_err(); | 
|  | 952 | +    // Here we're asking the native client to exit very early before it processed `env::vars()`; so | 
|  | 953 | +    // the execution makes it into Python code that calls | 
|  | 954 | +    // `PyNailgunClient(...).execute(command, args, modified_env)`. That's Rust code implementing a | 
|  | 955 | +    // Python extension object that also wrongly assumes utf8 when converting env vars. | 
|  | 956 | +    assert!(err.to_string().contains(concat!( | 
|  | 957 | +        r#"UnicodeEncodeError: 'utf-8' codec can't encode character '\udca5' in "#, | 
|  | 958 | +        "position 1: surrogates not allowed" | 
|  | 959 | +    ))); | 
|  | 960 | + | 
|  | 961 | +    let output = execute( | 
|  | 962 | +        Command::new(scie_pants_scie) | 
|  | 963 | +            .arg("--no-pantsd") | 
|  | 964 | +            .arg("-V") | 
|  | 965 | +            .env("PANTS_NO_NATIVE_CLIENT", "1") | 
|  | 966 | +            .stdout(Stdio::piped()) | 
|  | 967 | +            .current_dir(&tmpdir), | 
|  | 968 | +    ) | 
|  | 969 | +    .unwrap(); | 
|  | 970 | +    assert_eq!(pants_release, decode_output(output.stdout).unwrap().trim()); | 
|  | 971 | + | 
|  | 972 | +    env::remove_var("FOO"); | 
|  | 973 | +} | 
0 commit comments