Search code examples
rustgithub-actionsrust-cargoserdeactix-web

Cargo test pass on local machine but fails on github actions


I wrote a test to verify the POST was parsed correctly. Here is the endpoint:

#[post("/test_weather_data")]
pub(crate) async fn test_weather_data_post(weather_data: String) -> Result<String> {
    let wd: Vec<Reading> = serde_json::from_str(&*weather_data).unwrap();

    Ok(format!("id: {}, index: {}", &wd[0].id, &wd[0].index))
}

And the test:

#[actix_rt::test]
async fn test_weather_data_ok() {
    let mut app = test::init_service(App::new().service(controller::test_weather_data_post)).await;

    let payload = r#"[{"measurement_time_default":"2021-03-24T20:50:00+01:00","id":228,"index":201,"field_description":"relative_humidity","measurement":77.5}]"#;

    let resp = test::TestRequest::post()
        .uri("/test_weather_data")
        .header(header::CONTENT_TYPE, "application/json")
        .set_payload(payload)
        .send_request(&mut app)
        .await;

    let result = test::read_body(resp).await;

    assert_eq!(result, "id: 228, index: 201".as_bytes());
}

The struct:

#[derive(Deserialize, Insertable)]
pub struct Reading {
    #[serde(with = "my_date_format")]
    pub measurement_time_default: DateTime<Local>,
    pub id: i32,
    pub index: i32,
    pub field_description: String,
    pub measurement: f32,
}

Module my_date_format:

mod my_date_format {
    use chrono::{DateTime, Local, TimeZone};
    use serde::{self, Deserialize, Deserializer};

    const FORMAT: &str = "%+";

    pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Local>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Local
            .datetime_from_str(&s, FORMAT)
            .map_err(serde::de::Error::custom)
    }
}

cargo test passes on my local mac mini (M1) but fails on github actions with the error

thread 'test::test_weather_data_ok' panicked at 'called `Result::unwrap()` on an `Err` value: Error("no possible date and time matching input", line: 1, column: 56)', src/controller.rs:63:65

Here is my YML-file on github which is the default handed out by github:

name: Rust

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

env:
  CARGO_TERM_COLOR: always

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Build
      run: cargo build --verbose
    - name: Run tests
      run: cargo test --verbose

There is a SO-thread with a similar exit code 101 but the error in that case was due to directory layout changes so it doesn't seem to apply in this case.

Omitting the as_bytes() in the test gives the same error.


Solution

  • The documentation for chrono states that datetime_from_str will only succeed if there's no time zone in the text or the time zone matches the target TimeZone, which explains the failure if the time zone is different. The docs point to DateTime::parse_from_str to parse a DateTime with any time zone into a DateTime<FixedOffset> object, so something like this would work

    DateTime::parse_from_str(&s, FORMAT)
                .map(Into::into)
                .map_err(serde::de::Error::custom)
    

    Into::into is needed to map the type from DateTime<FixedOffset> to DateTime<Local> to match your return type.

    That said, chrono implements serialization directly with rfc3339 as the default format. You need to enable the serde feature flag. Then you can get rid of your custom deserialization code and just go with

    #[derive(Deserialize, Insertable)]
    pub struct Reading {
        pub measurement_time_default: DateTime<Local>,
        pub id: i32,
        pub index: i32,
        pub field_description: String,
        pub measurement: f32,
    }