serde-rs/test

serde_test limited by equality operator, not helpful when equality behaves differently

tspiteri opened this issue · 2 comments

I have a floating-point structure and I'm writing serde_test tests for it. However, I have two issues:

  1. There isn't a way to test its NaN deserialization, since NaN is defined as not equal to anything, not even itself.
  2. I cannot test that my +0.0 and -0.0 are deserialized distinctly, since +0.0 == -0.0.

In my case, I can test serialization properly as I'm serializing the values themselves as strings, but similar issues could arise for serialization if for example a test for primitive f32 NaNs is attempted.

Is there a way to go around this? Maybe provide functions similar to the following apart from the assert functions:

pub fn to_ser_tokens<T>(value: &T) -> Vec[Token] where T: Serialize;
pub fn from_de_tokens<T>(tokens: &[Token]) -> T where T: Deserialize;

The serde_test crate intentionally does not expose something like to_ser_tokens or from_de_tokens because we want an API that is useless for anything other than testing. Those functions would be too useful, in a sense.

How about using a type that overrides what comparison means for your test? Something like:

#[macro_use]
extern crate serde_derive;

extern crate serde;
extern crate serde_test;

use serde::{Deserialize, Deserializer};
use serde_test::Token;

// Pretend this is your type with wacky PartialEq.
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
enum Tspiteri {
    NaN,
    NegZero,
    PosZero,
    Other(u32),
}

impl PartialEq for Tspiteri {
    fn eq(&self, rhs: &Tspiteri) -> bool {
        use Tspiteri::*;
        match (*self, *rhs) {
            (NaN, _) | (_, NaN) => false,
            (NegZero, NegZero) |
            (NegZero, PosZero) |
            (PosZero, NegZero) |
            (PosZero, PosZero) => true,
            (Other(a), Other(b)) => a == b,
            _ => false,
        }
    }
}

#[derive(Debug)]
struct TestTspiteri(Tspiteri);

impl PartialEq for TestTspiteri {
    fn eq(&self, rhs: &TestTspiteri) -> bool {
        use Tspiteri::*;
        match (self.0, rhs.0) {
            (NaN, NaN) => true,
            (NegZero, NegZero) => true,
            (PosZero, PosZero) => true,
            (Other(a), Other(b)) => a == b,
            _ => false,
        }
    }
}

impl<'de> Deserialize<'de> for TestTspiteri {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        Tspiteri::deserialize(deserializer).map(TestTspiteri)
    }
}

fn main() {
    // bad test, passes
    serde_test::assert_de_tokens(&Tspiteri::NegZero, &[
        Token::UnitVariant { name: "Tspiteri", variant: "PosZero" },
    ]);

    // real test, fails
    serde_test::assert_de_tokens(&TestTspiteri(Tspiteri::NegZero), &[
        Token::UnitVariant { name: "Tspiteri", variant: "PosZero" },
    ]);
}

I can see the point of not exposing that functionality, and your solution is good, so I'm closing the bug. Thanks!