Cargo 测试在本地机器上通过,但在 github 操作上失败

Cargo test pass on local machine but fails on github actions

我写了一个测试来验证 POST 是否被正确解析。这是终点:

#[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))
}

测试:

#[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());
}

结构:

#[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,
}

模块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 通过我的本地 mac mini (M1) 但在 github actions 上失败并出现错误

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

这是我在 github 上的 YML 文件,这是 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

有一个 exit code 101 类似,但这种情况下的错误是由于目录布局更改造成的,因此它似乎不适用于这种情况。

在测试中省略 as_bytes() 会产生相同的错误。

看起来错误是在尝试使用 let wd: Vec<Reading> = serde_json::from_str(&*weather_data).unwrap(); 处的 serde 解析 json 时发生的,它似乎与 json 有效负载中的时间戳有关。通常 GitHub 操作以 UTC 执行,这可能与您本地计算机的时钟不同,这有时会导致错误。

也有可能每个平台支持的时间戳格式不同,但这取决于被反序列化的数据结构的定义,这在问题中没有显示

chrono 的文档指出 datetime_from_str 仅当文本中没有时区或时区与目标 TimeZone 匹配时才会成功,这解释了如果时区不同。文档指向 DateTime::parse_from_str 以将具有任何时区的 DateTime 解析为 DateTime<FixedOffset> 对象,所以这样的事情会起作用

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

Into::into 需要将类型从 DateTime<FixedOffset> 映射到 DateTime<Local> 以匹配您的 return 类型。

也就是说,chrono直接以rfc3339为默认格式实现序列化。您需要启用 serde 功能标志。然后你可以摆脱你的自定义反序列化代码,只需使用

#[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,
}