Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/wasi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ jobs:
# caret diagnostics; the host test crate does have them, hence the
# not(wasi_runner) in the cfg guarding every `mod diagnostics`.
# TODO: add integration tests for these tools as WASI support is extended:
# arch b2sum cksum csplit date dir dircolors fmt join
# arch b2sum cksum csplit dir dircolors fmt join
# ls md5sum mkdir mv nproc pathchk pr printenv ptx pwd readlink
# realpath rm rmdir seq sha1sum sha224sum sha256sum sha384sum
# sha512sum shred sleep sort split tsort uname uniq vdir
UUTESTS_BINARY_PATH="$(pwd)/target/${{ matrix.job.target }}/debug/coreutils.wasm" \
UUTESTS_WASM_RUNNER=wasmtime \
cargo test --test tests -- \
test_base32:: test_base64:: test_basenc:: test_basename:: \
test_cat:: test_comm:: test_cp:: test_cut:: test_dirname:: test_echo:: \
test_cat:: test_comm:: test_cp:: test_cut:: test_date:: test_dirname:: test_echo:: \
test_expand:: test_expr:: test_factor:: test_false:: test_fold:: \
test_head:: test_link:: test_ln:: \
test_ls::test_ls_a_dotdot_no_error_on_wasi \
Expand Down
3 changes: 3 additions & 0 deletions src/uu/date/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ rustix = { workspace = true, features = ["time"] }
# no environment injection).
jiff-tzdb = { workspace = true }

[target.'cfg(target_os = "wasi")'.dependencies]
libc = { workspace = true }

[target.'cfg(windows)'.dependencies]
windows-sys = { workspace = true, features = [
"Win32_Foundation",
Expand Down
1 change: 1 addition & 0 deletions src/uu/date/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ date-error-invalid-format = invalid format '{$format}' ({$error})
date-error-expected-file-got-directory = expected file, got directory {$path}
date-error-date-overflow = date overflow '{$date}'
date-error-setting-date-not-supported-redox = setting the date is not supported by Redox
date-error-setting-date-not-supported-wasi = setting the date is not supported by WASI
date-error-cannot-set-date = cannot set date
date-error-extra-operand = extra operand '{$operand}'
date-error-write = write error: {$error}
Expand Down
1 change: 1 addition & 0 deletions src/uu/date/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ date-error-invalid-format = format invalide '{$format}' ({$error})
date-error-expected-file-got-directory = fichier attendu, répertoire obtenu {$path}
date-error-date-overflow = débordement de date '{$date}'
date-error-setting-date-not-supported-redox = la définition de la date n'est pas prise en charge par Redox
date-error-setting-date-not-supported-wasi = la définition de la date n'est pas prise en charge par WASI
date-error-cannot-set-date = impossible de définir la date
date-error-extra-operand = opérande supplémentaire '{$operand}'
date-error-write = erreur d'écriture: {$error}
Expand Down
51 changes: 49 additions & 2 deletions src/uu/date/src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ enum DateError {
#[cfg(target_os = "redox")]
#[error("{}", translate!("date-error-setting-date-not-supported-redox"))]
SettingDateNotSupportedRedox,
#[cfg(target_os = "wasi")]
#[error("{}", translate!("date-error-setting-date-not-supported-wasi"))]
SettingDateNotSupportedWasi,
}

impl UError for DateError {}
Expand Down Expand Up @@ -1400,7 +1403,33 @@ fn parse_date<S: AsRef<str>>(
}
}

#[cfg(not(any(unix, windows)))]
#[cfg(target_os = "wasi")]
/// Returns the resolution of the system's realtime clock.
///
/// `rustix::time::clock_getres` excludes WASI, so `libc::clock_getres` is
/// used directly as a workaround.
fn get_clock_resolution() -> Timestamp {
let mut timespec = std::mem::MaybeUninit::<libc::timespec>::uninit();

// SAFETY: `clock_getres` writes a complete `timespec` into the provided
// pointer when it returns 0. We only read `timespec` after checking for
// success, so it is always fully initialized before use.
let timespec = unsafe {
Comment thread
eduardomourar marked this conversation as resolved.
let ret = libc::clock_getres(libc::CLOCK_REALTIME, timespec.as_mut_ptr());
assert_eq!(
ret,
0,
"clock_getres(CLOCK_REALTIME) failed: {}",
std::io::Error::last_os_error()
);
timespec.assume_init()
};

#[allow(clippy::unnecessary_cast, reason = "needed for 32 bit target")]
Timestamp::constant(timespec.tv_sec as _, timespec.tv_nsec as _)
}

#[cfg(not(any(unix, windows, target_os = "wasi")))]
fn get_clock_resolution() -> Timestamp {
unimplemented!("getting clock resolution not implemented (unsupported target)");
}
Expand Down Expand Up @@ -1439,7 +1468,7 @@ fn get_clock_resolution() -> Timestamp {
Timestamp::constant(0, 100)
}

#[cfg(not(any(unix, windows)))]
#[cfg(not(any(unix, windows, target_os = "wasi")))]
fn set_system_datetime(_date: Zoned) -> UResult<()> {
unimplemented!("setting date not implemented (unsupported target)");
}
Expand All @@ -1453,6 +1482,12 @@ fn convert_for_set(date: Zoned, utc: bool) -> Zoned {
}
}

#[cfg(target_os = "wasi")]
/// The WASI sandbox has no syscall for setting the wall clock.
fn set_system_datetime(_date: Zoned) -> UResult<()> {
Err(Box::new(DateError::SettingDateNotSupportedWasi))
Comment thread
eduardomourar marked this conversation as resolved.
}

#[cfg(target_os = "redox")]
fn set_system_datetime(_date: Zoned) -> UResult<()> {
Err(Box::new(DateError::SettingDateNotSupportedRedox))
Expand Down Expand Up @@ -1661,4 +1696,16 @@ mod tests {
"2020-03-12 05:30:00 +00:00\n"
);
}

#[test]
fn test_get_clock_resolution() {
let res = get_clock_resolution();

// Every supported platform reports a resolution of at least 1ns.
let nanos = res.as_second() as i128 * 1_000_000_000 + i128::from(res.subsec_nanosecond());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

res.as_nanosecond() already returns an i128, no need to compute it by hand

assert!(
(1..=1_000_000_000).contains(&nanos),
"unexpected clock resolution: {nanos}ns"
);
}
}
93 changes: 91 additions & 2 deletions tests/by-util/test_date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,10 @@ fn test_date_utc_with_d_flag() {
}

#[test]
#[cfg_attr(
wasi_runner,
ignore = "WASI sandbox: timezone/locale database not visible"
Comment thread
eduardomourar marked this conversation as resolved.
)]
fn test_date_utc_vs_local() {
let cases = [
("-d", "2024-01-01 12:00", "+%H:%M %Z", "12:00 EST\n"),
Expand Down Expand Up @@ -552,6 +556,10 @@ fn test_date_error_echoes_input_verbatim() {

#[test]
#[cfg(all(unix, not(target_os = "android")))]
#[cfg_attr(
wasi_runner,
ignore = "WASI: setting the system clock is not supported at all, not just permission-gated"
)]
fn test_date_set_permissions_error() {
if !(geteuid().is_root() || uucore::os::is_wsl_1()) {
let result = new_ucmd!()
Expand All @@ -568,6 +576,10 @@ fn test_date_set_permissions_error() {

#[test]
#[cfg(all(unix, not(target_os = "android")))]
#[cfg_attr(
wasi_runner,
ignore = "WASI: setting the system clock is not supported at all, not just permission-gated"
)]
fn test_date_set_hyphen_prefixed_values() {
// test -s flag accepts hyphen-prefixed values like "-3 days"
if !(geteuid().is_root() || uucore::os::is_wsl_1()) {
Expand All @@ -594,6 +606,16 @@ fn test_date_set_hyphen_prefixed_values() {
}
}

#[test]
#[cfg(wasi_runner)]
fn test_date_set_unsupported_on_wasi() {
new_ucmd!()
.arg("--set")
.arg("2020-03-11 21:45:00+08:00")
.fails_with_code(1)
.stderr_is("date: setting the date is not supported by WASI\n");
}

#[test]
#[cfg(unix)]
fn test_date_set_valid_2() {
Expand Down Expand Up @@ -1041,8 +1063,11 @@ fn test_date_parse_from_format() {
2023-04-01 12:00:00\n\
2023-04-15 18:30:00",
);
// Pass the file relative to the command's working directory. On WASI the
// preopened sandbox root is not the same as the host absolute path, so an
// absolute `at.plus(FILE)` argument would not resolve inside the sandbox.
ucmd.arg("-f")
.arg(at.plus(FILE))
.arg(FILE)
Comment thread
eduardomourar marked this conversation as resolved.
.arg("+%Y-%m-%d %H:%M:%S")
.succeeds();
}
Expand Down Expand Up @@ -1070,6 +1095,10 @@ const JAN2: &str = "2024-01-02 12:00:00 +0000";
const JUL2: &str = "2024-07-02 12:00:00 +0000";

#[test]
#[cfg_attr(
wasi_runner,
ignore = "WASI sandbox: timezone/locale database not visible"
)]
fn test_date_tz() {
fn test_tz(tz: &str, date: &str, output: &str) {
println!("Test with TZ={tz}, date=\"{date}\".");
Expand Down Expand Up @@ -1116,6 +1145,10 @@ fn test_date_tz_with_utc_flag() {
}

#[test]
#[cfg_attr(
wasi_runner,
ignore = "WASI sandbox: timezone/locale database not visible"
)]
fn test_date_tz_various_formats() {
fn test_tz(tz: &str, date: &str, output: &str) {
println!("Test with TZ={tz}, date=\"{date}\".");
Expand Down Expand Up @@ -1144,6 +1177,10 @@ fn test_date_tz_various_formats() {
}

#[test]
#[cfg_attr(
wasi_runner,
ignore = "WASI sandbox: timezone/locale database not visible"
)]
fn test_date_tz_with_relative_time() {
new_ucmd!()
.env("TZ", "America/Vancouver")
Expand All @@ -1155,6 +1192,10 @@ fn test_date_tz_with_relative_time() {
}

#[test]
#[cfg_attr(
wasi_runner,
ignore = "WASI sandbox: timezone/locale database not visible"
)]
fn test_date_utc_time() {
// Test that -u flag shows correct UTC time
// We get 2 UTC times just in case we're really unlucky and this runs around
Expand Down Expand Up @@ -1651,6 +1692,10 @@ fn test_date_whitespace_between_items() {
}

#[test]
#[cfg_attr(
wasi_runner,
ignore = "WASI sandbox: timezone/locale database not visible"
)]
fn test_date_relative_m9() {
// Military timezone "m9" should be parsed as noon + 9 hours = 21:00 UTC
// When displayed in TZ=UTC+9 (which is UTC-9), this shows as 12:00 local time
Expand Down Expand Up @@ -1948,6 +1993,10 @@ fn test_date_locale_en_us_vs_c_difference() {

#[test]
#[cfg(unix)]
#[cfg_attr(
wasi_runner,
ignore = "WASI sandbox: timezone/locale database not visible"
)]
fn test_date_locale_hu_hungarian() {
// Regression test for uutils/coreutils#11240: the GNU modifier fast-path
// ("%-e") used to run before ICU localization, so "%b"/"%A" came out in
Expand Down Expand Up @@ -2239,6 +2288,10 @@ fn test_date_input_hhmm_ampm() {
}

#[test]
#[cfg_attr(
wasi_runner,
ignore = "WASI sandbox: timezone/locale database not visible"
)]
fn test_date_input_trailing_tz_abbrev_rezones() {
// `TZ=UTC+1 date -d '2024-01-01 EST'` should display the instant in UTC+1
// (GNU: 04:00:00 -01:00), not leave it in EST (the pre-fix uutils
Expand Down Expand Up @@ -2367,6 +2420,10 @@ fn test_date_parenthesis_vs_other_special_chars() {

#[test]
#[cfg(unix)]
#[cfg_attr(
wasi_runner,
ignore = "WASI sandbox: timezone/locale database not visible"
)]
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
Expand Down Expand Up @@ -2434,6 +2491,10 @@ fn test_date_iranian_locale_solar_hijri_calendar() {

#[test]
#[cfg(unix)]
#[cfg_attr(
wasi_runner,
ignore = "WASI sandbox: timezone/locale database not visible"
)]
fn test_date_ethiopian_locale_calendar() {
// Test Ethiopian locale uses Ethiopian calendar
// Verify the Ethiopian calendar is used in the Ethiopian locale
Expand Down Expand Up @@ -2581,6 +2642,10 @@ fn check_date(locale: &str, date: &str, fmt: &str, expected: &str) {

#[test]
#[cfg(unix)]
#[cfg_attr(
wasi_runner,
ignore = "WASI sandbox: timezone/locale database not visible"
)]
fn test_locale_calendar_conversions() {
// Persian (Solar Hijri) - Nowruz is March 20/21
for (d, e) in [
Expand Down Expand Up @@ -2632,6 +2697,10 @@ fn test_locale_calendar_conversions() {

#[test]
#[cfg(unix)]
#[cfg_attr(
wasi_runner,
ignore = "WASI sandbox: timezone/locale database not visible"
)]
fn test_locale_month_names() {
// %B full month names: Jan, Jun, Dec for each locale
for (loc, jan, jun, dec) in [
Expand All @@ -2652,6 +2721,10 @@ fn test_locale_month_names() {

#[test]
#[cfg(unix)]
#[cfg_attr(
wasi_runner,
ignore = "WASI sandbox: timezone/locale database not visible"
)]
fn test_locale_abbreviated_month_names() {
// %b abbreviated month names: Feb, Jun, Dec for each locale
// This test ensures we don't get double periods in locales like Hungarian
Expand All @@ -2675,6 +2748,10 @@ fn test_locale_abbreviated_month_names() {

#[test]
#[cfg(unix)]
#[cfg_attr(
wasi_runner,
ignore = "WASI sandbox: timezone/locale database not visible"
)]
fn test_locale_day_names() {
// %A full day names: Mon (26th), Sun (25th), Sat (24th) Jan 2026
for (loc, mon, sun, sat) in [
Expand All @@ -2692,6 +2769,10 @@ fn test_locale_day_names() {

#[test]
#[cfg(unix)]
#[cfg_attr(
wasi_runner,
ignore = "WASI sandbox: timezone/locale database not visible"
)]
fn test_locale_names_for_several_dates_in_one_run() {
let (at, mut ucmd) = at_and_ucmd!();
at.write("dates", "2026-01-26\n2026-06-14\n2026-12-12\n");
Expand Down Expand Up @@ -2787,8 +2868,11 @@ fn test_date_month_subtraction_keeps_day() {

// Tests for embedded timezone parsing
#[test]
#[cfg_attr(
wasi_runner,
ignore = "WASI sandbox: timezone/locale database not visible"
)]
fn test_date_embedded_timezone_conversion() {
// Parse date with embedded timezone
// Date should be interpreted in embedded TZ, then displayed in environment TZ
new_ucmd!()
.env("TZ", "UTC0")
Expand All @@ -2803,6 +2887,7 @@ fn test_date_embedded_timezone_conversion() {
// Tests for invalid UTF-8 in date string
#[test]
#[cfg(unix)]
#[cfg_attr(wasi_runner, ignore = "WASI: argv must be valid UTF-8")]
fn test_date_invalid_utf8_byte_rejected() {
use std::os::unix::ffi::OsStrExt;

Expand Down Expand Up @@ -3449,6 +3534,7 @@ fn test_date_allow_spaces_after_month() {

#[test]
#[cfg(unix)]
#[cfg_attr(wasi_runner, ignore = "WASI: argv must be valid UTF-8")]
fn test_format_with_non_utf8_bytes() {
use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
Expand All @@ -3467,6 +3553,7 @@ fn test_format_with_non_utf8_bytes() {

#[test]
#[cfg(unix)]
#[cfg_attr(wasi_runner, ignore = "WASI: argv must be valid UTF-8")]
fn test_format_percent_before_non_utf8_byte() {
use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
Expand All @@ -3484,6 +3571,7 @@ fn test_format_percent_before_non_utf8_byte() {

#[test]
#[cfg(unix)]
#[cfg_attr(wasi_runner, ignore = "WASI: argv must be valid UTF-8")]
fn test_format_with_gb18030_bytes() {
use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
Expand All @@ -3502,6 +3590,7 @@ fn test_format_with_gb18030_bytes() {

#[test]
#[cfg(unix)]
#[cfg_attr(wasi_runner, ignore = "WASI: argv must be valid UTF-8")]
fn test_non_utf8_operands_are_octal_escaped() {
use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
Expand Down
Loading