blob: b925d72d7572b83664edd30978f0c7b1659371e6 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! See the `README.md` file in this directory for more detail.
#[cfg(test)]
mod tests {
use {
anyhow::{Context as _, Error},
crossbeam::channel,
fidl_fidl_examples_echo as fecho, fidl_fuchsia_intl as fintl,
fidl_fuchsia_settings as fsettings, fuchsia_async as fasync,
fuchsia_async::DurationExt,
fuchsia_component::client,
fuchsia_syslog::{self as syslog, macros::*},
fuchsia_zircon as zx, icu_data, rust_icu_ucal as ucal, rust_icu_udat as udat,
rust_icu_uloc as uloc, rust_icu_ustring as ustring,
std::convert::TryFrom,
};
/// Sets a timezone for the duration of its lifetime, and restores the previous timezone at the
/// end.
pub struct ScopedTimezone {
timezone: String,
client: fsettings::IntlProxy,
}
fn intl_client() -> Result<fsettings::IntlProxy, Error> {
client::connect_to_service::<fsettings::IntlMarker>()
.context("Failed to connect to fuchsia.settings.Intl")
}
impl ScopedTimezone {
/// Tries to create a new timezone setter.
pub async fn try_new(timezone: &str) -> Result<ScopedTimezone, Error> {
let client = intl_client()?;
let response = client.watch().await?;
fx_log_info!("setting timezone for test: {}", timezone);
let old_timezone = response.time_zone_id.unwrap().id;
let setter = ScopedTimezone { timezone: old_timezone, client };
setter.set_timezone(timezone).await?;
Ok(setter)
}
/// Asynchronously set the timezone based to the supplied value.
async fn set_timezone(&self, timezone: &str) -> Result<(), Error> {
fx_log_info!("setting timezone: {}", timezone);
let settings = fsettings::IntlSettings {
time_zone_id: Some(fintl::TimeZoneId { id: timezone.to_string() }),
locales: None,
temperature_unit: None,
hour_cycle: None,
};
let _result = self.client.set(settings).await.context("setting timezone").unwrap();
Ok(())
}
}
impl Drop for ScopedTimezone {
/// Restores the previous timezone setting on the system.
fn drop(&mut self) {
let timezone = self.timezone.clone();
let (tx, rx) = channel::bounded(0);
// This piece of gymnastics is needed because the FIDL call we make
// here is async, but `drop` implementation must be sync.
std::thread::spawn(move || {
let mut executor = fasync::Executor::new().unwrap();
executor.run_singlethreaded(async move {
fx_log_info!("restoring timezone: {}", &timezone);
let client = intl_client().unwrap();
let settings = fsettings::IntlSettings {
time_zone_id: Some(fintl::TimeZoneId { id: timezone }),
locales: None,
temperature_unit: None,
hour_cycle: None,
};
let _result = client.set(settings).await.context("restoring timezone");
tx.send(()).unwrap();
});
});
rx.recv().unwrap();
fx_log_info!("restored timezone: {}", &self.timezone);
}
}
// Implements the Echo service which serves an abbreviated form of current time.
static DART_TIME_SERVICE_URL: &str =
"fuchsia-pkg://fuchsia.com/timestamp-server-dart#meta/timestamp-server-dart.cmx";
// The test will set one of these timezones to be the "system" timezone.
static TIMEZONE_NAME: &str = "America/Los_Angeles";
static TIMEZONE_NAME_2: &str = "America/New_York";
/// Polls the echo server until either the local and remote time match, or a timeout
/// occurs.
async fn loop_until_matching_time(
fmt: &udat::UDateFormat,
echo: &fidl_fidl_examples_echo::EchoProxy,
) -> Result<(), Error> {
const MAX_ATTEMPTS: usize = 10;
let sleep = zx::Duration::from_millis(1000);
// Multiple attempts in case the test is run close to the turn of the hour.
for attempt in 1..=MAX_ATTEMPTS {
fx_log_info!("Requesting some time, attempt: {}", attempt);
let out = echo
.echo_string(Some("Gimme some time!"))
.await
.with_context(|| "echo_string failed")?;
let date_time = fmt.format(ucal::get_now())?;
let vm_time = out.unwrap();
if date_time == vm_time {
break;
}
if (date_time != vm_time) && attempt == MAX_ATTEMPTS {
return Err(anyhow::anyhow!(
"dart VM says the time is {:?}, but the test fixture says it should be: {:?}",
vm_time,
date_time
));
}
fasync::Timer::new(sleep.after_now()).await;
}
Ok(())
}
/// Starts a dart program that uses Dart's idea of the system time zone to report time zone
/// information. The test fixture compares its own idea of local time with the one in the dart
/// VM.
///
/// Ostensibly, those two times should be the same up to the current date and current hour.
///
/// We do this twice, to ensure that runtime timezone changes are reflected in the time
/// reported by the dart VM.
#[fasync::run_singlethreaded(test)]
async fn check_reported_time_in_dart_vm() -> Result<(), Error> {
syslog::init_with_tags(&["check_reported_time_in_dart_vm"]).context("Can't init logger")?;
let _icu_data_loader =
icu_data::Loader::new().with_context(|| "could not load ICU data")?;
let _setter = ScopedTimezone::try_new(TIMEZONE_NAME).await.unwrap();
let locale = uloc::ULoc::try_from("Etc/Unknown").unwrap();
// Example: "2020-2-26-14", hour 14 of February 26.
let pattern = ustring::UChar::try_from("yyyy-M-d-H").unwrap();
let formatter = udat::UDateFormat::new_with_pattern(
&locale,
&ustring::UChar::try_from(TIMEZONE_NAME).unwrap(),
&pattern,
)
.unwrap();
let launcher = client::launcher().context("Failed to get the launcher")?;
let app = client::launch(&launcher, DART_TIME_SERVICE_URL.to_string(), None)
.context("failed to launch the dart service under test")?;
let echo = app
.connect_to_service::<fecho::EchoMarker>()
.context("Failed to connect to echo service")?;
loop_until_matching_time(&formatter, &echo)
.await
.expect("local and remote times should match eventually");
{
// Change the time zone again, and verify that the dart VM has seen the change
// too.
let _setter = ScopedTimezone::try_new(TIMEZONE_NAME_2).await.unwrap();
let formatter = udat::UDateFormat::new_with_pattern(
&locale,
&ustring::UChar::try_from(TIMEZONE_NAME_2).unwrap(),
&pattern,
)
.unwrap();
loop_until_matching_time(&formatter, &echo)
.await
.expect("local and remote times should match eventually");
}
Ok(())
}
#[fasync::run_singlethreaded(test)]
#[ignore] // See http://fxb/58383
async fn check_reported_time_in_dart_vm_no_update() -> Result<(), Error> {
syslog::init_with_tags(&["check_reported_time_in_dart_vm_no_update"])
.context("Can't init logger")?;
let _icu_data_loader =
icu_data::Loader::new().with_context(|| "could not load ICU data")?;
let _setter = ScopedTimezone::try_new(TIMEZONE_NAME).await.unwrap();
let locale = uloc::ULoc::try_from("Etc/Unknown").unwrap();
// Example: "2020-2-26-14", hour 14 of February 26.
let pattern = ustring::UChar::try_from("yyyy-M-d-H").unwrap();
let formatter = udat::UDateFormat::new_with_pattern(
&locale,
&ustring::UChar::try_from(TIMEZONE_NAME).unwrap(),
&pattern,
)
.unwrap();
let launcher = client::launcher().context("Failed to get the launcher")?;
let app = client::launch(&launcher, DART_TIME_SERVICE_URL.to_string(), None)
.context("failed to launch the dart service under test")?;
let echo = app
.connect_to_service::<fecho::EchoMarker>()
.context("Failed to connect to echo service")?;
loop_until_matching_time(&formatter, &echo)
.await
.expect("local and remote times should match eventually");
Ok(())
}
} // tests
// Makes fargo happy.
fn main() {}