tests: deduplicate locale availability checks into shared is_locale_available()

This commit is contained in:
Sylvestre Ledru
2026-03-21 23:09:48 +01:00
committed by Daniel Hofstetter
parent 1658fc8f3d
commit ffa088d467
3 changed files with 140 additions and 88 deletions
+5 -42
View File
@@ -13,6 +13,8 @@ use regex::Regex;
#[cfg(all(unix, not(target_os = "macos")))]
use uucore::process::geteuid;
use uutests::util::TestScenario;
#[cfg(unix)]
use uutests::util::is_locale_available;
use uutests::{at_and_ucmd, new_ucmd, util_name};
#[test]
@@ -1840,20 +1842,7 @@ fn test_date_parenthesis_vs_other_special_chars() {
fn test_date_iranian_locale_solar_hijri_calendar() {
// Test Iranian locale uses Solar Hijri calendar
// Verify the Solar Hijri calendar is used in the Iranian locale
use std::process::Command;
// Check if Iranian locale is available
let locale_check = Command::new("locale")
.env("LC_ALL", "fa_IR.UTF-8")
.arg("charmap")
.output();
let locale_available = match locale_check {
Ok(output) => String::from_utf8_lossy(&output.stdout).trim() == "UTF-8",
Err(_) => false,
};
if !locale_available {
if !is_locale_available("fa_IR.UTF-8") {
println!("Skipping Iranian locale test - fa_IR.UTF-8 locale not available");
return;
}
@@ -1920,20 +1909,7 @@ fn test_date_iranian_locale_solar_hijri_calendar() {
fn test_date_ethiopian_locale_calendar() {
// Test Ethiopian locale uses Ethiopian calendar
// Verify the Ethiopian calendar is used in the Ethiopian locale
use std::process::Command;
// Check if Ethiopian locale is available
let locale_check = Command::new("locale")
.env("LC_ALL", "am_ET.UTF-8")
.arg("charmap")
.output();
let locale_available = match locale_check {
Ok(output) => String::from_utf8_lossy(&output.stdout).trim() == "UTF-8",
Err(_) => false,
};
if !locale_available {
if !is_locale_available("am_ET.UTF-8") {
println!("Skipping Ethiopian locale test - am_ET.UTF-8 locale not available");
return;
}
@@ -2000,20 +1976,7 @@ fn test_date_ethiopian_locale_calendar() {
fn test_date_thai_locale_solar_calendar() {
// Test Thai locale uses Thai solar calendar
// Verify the Thai solar calendar is used with the Thai locale
use std::process::Command;
// Check if Thai locale is available
let locale_check = Command::new("locale")
.env("LC_ALL", "th_TH.UTF-8")
.arg("charmap")
.output();
let locale_available = match locale_check {
Ok(output) => String::from_utf8_lossy(&output.stdout).trim() == "UTF-8",
Err(_) => false,
};
if !locale_available {
if !is_locale_available("th_TH.UTF-8") {
println!("Skipping Thai locale test - th_TH.UTF-8 locale not available");
return;
}
+122 -46
View File
@@ -3,7 +3,7 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (words) ints (linux) NOFILE dfgi
// spell-checker:ignore (words) ints (linux) NOFILE dfgi abmon avril
#![allow(clippy::cast_possible_wrap)]
use std::env;
@@ -15,6 +15,8 @@ use std::time::Duration;
use uutests::at_and_ucmd;
use uutests::new_ucmd;
use uutests::util::TestScenario;
#[cfg(unix)]
use uutests::util::is_locale_available;
fn test_helper(file_name: &str, possible_args: &[&str]) {
for args in possible_args {
@@ -603,44 +605,106 @@ fn test_month_default2() {
}
}
#[test]
fn test_month_sort_french_locale() {
// Test locale-aware month sorting (mirrors GNU sort-month.sh test).
// Uses French UTF-8 locale where abbreviated months are:
// spell-checker:disable-next-line
// janv. févr. mars avril mai juin juil. août sept. oct. nov. déc.
let result = new_ucmd!()
.env("LC_ALL", "fr_FR.UTF-8")
.arg("-M")
// spell-checker:disable-next-line
.pipe_in("mai\ndéc.\njanv.\njuin\nfévr.\nmars\navril\njuil.\naoût\nsept.\noct.\nnov.\n")
.run();
// Skip if locale is not available on the system
if result.succeeded() {
assert_eq!(
result.stdout_str(),
"janv.\nfévr.\nmars\navril\nmai\njuin\njuil.\naoût\nsept.\noct.\nnov.\ndéc.\n",
);
/// Query the system for abbreviated month names via `locale abmon`.
/// Returns a vector of 12 month abbreviations in order (Jan..Dec),
/// or None if the command fails or returns unexpected output.
#[cfg(any(target_vendor = "apple", target_os = "openbsd"))]
fn get_system_abmon(locale: &str) -> Option<Vec<String>> {
let output = Command::new("locale")
.env("LC_ALL", locale)
.arg("abmon")
.output()
.ok()?;
if !output.status.success() {
return None;
}
let text = String::from_utf8(output.stdout).ok()?;
let months: Vec<String> = text
.trim()
.split(';')
.map(String::from)
.filter(|m| !m.is_empty())
.collect();
if months.len() == 12 {
Some(months)
} else {
None
}
}
#[test]
fn test_month_sort_hungarian_locale() {
// Hungarian abbreviated months: jan febr márc ápr máj jún júl aug szept okt nov dec
let result = new_ucmd!()
.env("LC_ALL", "hu_HU.UTF-8")
.arg("-M")
// spell-checker:disable-next-line
.pipe_in("máj\ndec\njan\njún\nfebr\nmárc\nápr\njúl\naug\nszept\nokt\nnov\n")
.run();
/// Build shuffled input and sorted expected output from month names.
#[cfg(any(target_vendor = "apple", target_os = "openbsd"))]
fn month_sort_input_expected(months: &[String]) -> (String, String) {
// Shuffled order: May, Dec, Jan, Jun, Feb, Mar, Apr, Jul, Aug, Sep, Oct, Nov
let shuffle_order = [4, 11, 0, 5, 1, 2, 3, 6, 7, 8, 9, 10];
let input = shuffle_order
.iter()
.map(|&i| months[i].as_str())
.collect::<Vec<_>>()
.join("\n")
+ "\n";
let expected = months.join("\n") + "\n";
(input, expected)
}
if result.succeeded() {
assert_eq!(
result.stdout_str(),
"jan\nfebr\nmárc\nápr\nmáj\njún\njúl\naug\nszept\nokt\nnov\ndec\n",
);
#[test]
#[cfg(unix)]
fn test_month_sort_french_locale() {
let locale = "fr_FR.UTF-8";
if !is_locale_available(locale) {
return;
}
// spell-checker:disable
// On macOS/OpenBSD, abbreviated month names vary across OS versions (different CLDR data),
// so we query the system dynamically. On other platforms, glibc values are stable.
#[cfg(any(target_vendor = "apple", target_os = "openbsd"))]
let (input, expected) = {
let Some(months) = get_system_abmon(locale) else {
return;
};
month_sort_input_expected(&months)
};
#[cfg(not(any(target_vendor = "apple", target_os = "openbsd")))]
let (input, expected) = (
"mai\ndéc.\njanv.\njuin\nfévr.\nmars\navril\njuil.\naoût\nsept.\noct.\nnov.\n".to_string(),
"janv.\nfévr.\nmars\navril\nmai\njuin\njuil.\naoût\nsept.\noct.\nnov.\ndéc.\n".to_string(),
);
// spell-checker:enable
new_ucmd!()
.env("LC_ALL", locale)
.arg("-M")
.pipe_in(input)
.succeeds()
.stdout_is(expected);
}
#[test]
#[cfg(unix)]
fn test_month_sort_hungarian_locale() {
let locale = "hu_HU.UTF-8";
if !is_locale_available(locale) {
return;
}
// spell-checker:disable
#[cfg(any(target_vendor = "apple", target_os = "openbsd"))]
let (input, expected) = {
let Some(months) = get_system_abmon(locale) else {
return;
};
month_sort_input_expected(&months)
};
#[cfg(not(any(target_vendor = "apple", target_os = "openbsd")))]
let (input, expected) = (
"máj\ndec\njan\njún\nfebr\nmárc\nápr\njúl\naug\nszept\nokt\nnov\n".to_string(),
"jan\nfebr\nmárc\nápr\nmáj\njún\njúl\naug\nszept\nokt\nnov\ndec\n".to_string(),
);
// spell-checker:enable
new_ucmd!()
.env("LC_ALL", locale)
.arg("-M")
.pipe_in(input)
.succeeds()
.stdout_is(expected);
}
/// Test that embedded blanks in month names cause a non-match (GNU compat).
@@ -698,20 +762,32 @@ fn test_month_sort_french_embedded_blanks() {
}
#[test]
#[cfg(unix)]
fn test_month_sort_japanese_locale() {
// Japanese abbreviated months have leading spaces: " 1月" " 2月" ... "10月" "11月" "12月"
let result = new_ucmd!()
.env("LC_ALL", "ja_JP.UTF-8")
.arg("-M")
.pipe_in("5月\n12月\n1月\n6月\n2月\n3月\n4月\n7月\n8月\n9月\n10月\n11月\n")
.run();
if result.succeeded() {
assert_eq!(
result.stdout_str(),
"1月\n2月\n3月\n4月\n5月\n6月\n7月\n8月\n9月\n10月\n11月\n12月\n",
);
let locale = "ja_JP.UTF-8";
if !is_locale_available(locale) {
return;
}
// On macOS/OpenBSD, abbreviated month names may differ, so query dynamically.
#[cfg(any(target_vendor = "apple", target_os = "openbsd"))]
let (input, expected) = {
let Some(months) = get_system_abmon(locale) else {
return;
};
month_sort_input_expected(&months)
};
// Japanese abbreviated months are numeric (1月..12月) on glibc
#[cfg(not(any(target_vendor = "apple", target_os = "openbsd")))]
let (input, expected) = (
"5月\n12月\n1月\n6月\n2月\n3月\n4月\n7月\n8月\n9月\n10月\n11月\n".to_string(),
"1月\n2月\n3月\n4月\n5月\n6月\n7月\n8月\n9月\n10月\n11月\n12月\n".to_string(),
);
new_ucmd!()
.env("LC_ALL", locale)
.arg("-M")
.pipe_in(input)
.succeeds()
.stdout_is(expected);
}
#[test]
+13
View File
@@ -100,6 +100,19 @@ pub fn is_ci() -> bool {
env::var("CI").is_ok_and(|s| s.eq_ignore_ascii_case("true"))
}
/// Check if a locale is available on the system by verifying that
/// `locale charmap` returns `"UTF-8"` when `LC_ALL` is set to the given locale.
#[cfg(unix)]
pub fn is_locale_available(locale: &str) -> bool {
use std::process::Command;
Command::new("locale")
.env("LC_ALL", locale)
.arg("charmap")
.output()
.map(|o| String::from_utf8_lossy(&o.stdout).trim() == "UTF-8")
.unwrap_or(false)
}
/// Read a test scenario fixture, returning its bytes
fn read_scenario_fixture<S: AsRef<OsStr>>(tmpd: Option<&Rc<TempDir>>, file_rel_path: S) -> Vec<u8> {
let tmpdir_path = tmpd.as_ref().unwrap().as_ref().path();