-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
test: proptests for round trip serialisation of Type
s and Op
s.
#981
Conversation
ded0695
to
e094512
Compare
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #981 +/- ##
==========================================
+ Coverage 86.20% 86.55% +0.34%
==========================================
Files 82 83 +1
Lines 17236 17547 +311
Branches 17236 17547 +311
==========================================
+ Hits 14859 15188 +329
+ Misses 1556 1539 -17
+ Partials 821 820 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
Somehow |
01e7a51
to
c17885d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, many thanks @doug-q . It's a shame the code to come up with instances has to be so tricky but I think that's an inevitable result of using Rust rather than Haskell here ;-). My main thoughts are that
- I don't really understand the details of the
prop_recursive
macros.... - but maybe that's an easier way than having
TypeDepth
??
@@ -64,6 +62,25 @@ include_schema!( | |||
"../../../../specification/schema/testing_hugr_schema_strict_v1.json" | |||
); | |||
|
|||
macro_rules! impl_sertesting_from { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, I was kinda hoping we wouldn't need this anymore! Ah well
hugr/src/hugr/serialize/test.rs
Outdated
op: optype.into(), | ||
}); | ||
#[test] | ||
fn prop_roundtrip_optype(op in ((0..(std::u32::MAX / 2) as usize). prop_map(|x| portgraph::NodeIndex::new(x).into()), any::<Option<ExtensionSet>>(), any::<OpType>()).prop_map(|(parent, input_extensions, op)| NodeSer { parent, input_extensions, op })) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a little hard to read...is it possible to outline this NodeSer
-factory? I think the overall is fn prop_roundtrip_optype(op in <thing>)
which is presumably a proptest-specific construct, but maybe that can be an Iterator<NodeSer>
or something? (A shame to have to construct NodeSer
s really - ideally prop_roundtrip_optype
should take an Optype, but I guess that doesn't fit the serialization format?)
hugr/src/ops/constant.rs
Outdated
let leaf_strat = prop_oneof![ | ||
any::<ExtensionValue>().prop_map(|e| Self::Extension { e }), | ||
prop_oneof![ | ||
// TODO we need an example of each legal root, in particular FuncDe{fn,cl} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reckon in future we will have impl Arbitrary for Hugr
- nothing says it has to be able to generate any Hugr, but we could cover a good cross-section - but very happy not to do that in this PR
].sboxed() | ||
}; | ||
|
||
static ref ANY_SERDE_YAML_VALUE_LEAF: SBoxedStrategy<serde_yaml::Value> = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whoa, hang on. Surely we should not be constructing YAML this way. Arbitrary Rust object, serialize, validate against schema, deserialize, check equality ...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is used for the contents of CustomTypeArg
and CustomSerialised
, which really can be arbitrary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah got it. I think that's worth a comment.
hugr/src/types.rs
Outdated
use crate::types::{CustomType, FunctionType, SumType, TypeBound, TypeEnum, TypeRow}; | ||
use ::proptest::prelude::*; | ||
impl Arbitrary for super::TypeEnum { | ||
type Parameters = TypeDepth; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presumably you've done this because constructing arbitrary instances without enforcing a depth limit leaves to overly-massive/deep structures??
I wonder if an easier way might be just to probabilistically instruct the framework to prefer the leaf constructors over those with more nested components. Maybe that doesn't, or is hard to tune to, give sufficient coverage of the nested components!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you perhaps use the prop_recursive
thing that appears to have a limit on depth and a target total size? Might that be a much easier way of getting structures of the shape you want?
justfile
Outdated
"poetry run pytest" | ||
) | ||
|
||
proptest: | ||
cargo test --all-features |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
R u sure you want to turn extension_inference
on here too? ;-)
I guess this makes sense but I'm not 100% sure about the naming
proptest! { | ||
#[test] | ||
fn prop_roundtrip_type(t: Type) { | ||
check_testing_roundtrip(t) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yay :). I was a bit confused when I looked at the wealth of methods with roundtrip
in the name (it might help to rename ser_roundtrip_validate
to ser_schema_roundtrip
as it does not call validate
), but this one looks to do what we want :-). You could remove the allow_unused
on its declaration now!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does not call validate, but it does validate against a schema. Leaving as is for now.
....but maybe I haven't understood this. You mean that I was going to say that Hugr doesn't have recursive Types, but realize now that that's not useful... The random type variables is fine as long as we don't |
fe3450d
to
c223a56
Compare
c8fdc48
to
dbacfe0
Compare
74c6797
to
507d9c0
Compare
3bc5749
to
8b7d7f4
Compare
…tion (#1005) We fix `model_rebuild` in `tys.py` to update the `model_config` rather than overwrite it. This prevents our config, i.e. `json_scheme_extra.required` from being removed during a `model_rebuild`. We remove most `json_scheme_extra.required` from the schema, using these only for `RootModel`s. This allows us to remove `TaggedSumType`, as well as alleviating us from the need of introducing `TaggedOpaqueType`. The serialisation schema is updated, and is `proptest`ed in #981. Reviewers should verify that the `serde` annotations, the pydantic schema definition, and the generated schemas exactly match that branch. BREAKING CHANGE: * Serialization schema * `Const::const_type` and `Value::const_type` are renamed to `Const::get_type` and `Value::get_type`. These now match several other `get_type` functions. --------- Co-authored-by: Craig Roy <[email protected]>
9d9e560
to
4d4fe77
Compare
Type
s and Op
s.
4d4fe77
to
5305fd2
Compare
@acl-cqc I've addressed all your comments and tidied things up a bit. I've used the Also, I have enabled the "proptest" feature by default, because the proptesting is running in <1sec. CI build times are not a concern because caching. Perhaps we should remove the feature flag entirely? @ss2165 I will add |
The feature flag seems useful for dependencies to optionally use hugrs in their own proptests? |
hugr/Cargo.toml
Outdated
@@ -22,7 +22,9 @@ bench = false | |||
path = "src/lib.rs" | |||
|
|||
[features] | |||
default = ["proptest"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to turn the proptest feature on only when #[cfg(test)]
is true, maybe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also pondering how well this agrees with what's in justfile
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No blockers, the proptest package doesn't give the easiest code to follow but I think I got there in the end. A few suggestions/places where comments might help. Also you should really global replace "heirarchy" with "hierarchy", the former is not a word in English ("heir" is about passing on your possessions when you die, "hierarchy" comes from Greek "hieros" = "ruler")...
-
RecursionDepth might be a good basis for adding legal typeparams, etc., in the future :). Are we sure we don't want to add a TypeBound in there (subsuming CustomTypeParameters)?
-
Those --feature flags are doing what we expect, right?
cargo test
on its own does not execute proptests?
fn roundtrip_value(#[case] value: Value) { | ||
check_testing_roundtrip(value); | ||
} | ||
proptest! { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we remove all the manual cases here. How confident are we that proptest covers the interesting ones? (And, what about the commented-out cases that fail. Do they no longer fail?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a good point
@@ -165,6 +166,7 @@ impl From<ExtensionOp> for CustomOp { | |||
/// | |||
/// [Extension]: crate::Extension | |||
#[derive(Clone, Debug)] | |||
// #[cfg_attr(test, derive(proptest_derive::Arbitrary))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment why not, or maybe you can enable this?
hugr/src/proptest.rs
Outdated
/// [RecursionDepth]. | ||
/// | ||
/// Consider a path between two different nodes of the same type(e.g. two [Type]s, or two | ||
/// [FunctionType]s). The path must be non-increasing in [RecursionDepth] because |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
non-increasing works as long as there is some probability of a decrease, and we'll probabilistically keep generating until we hit the option that decreases. That's not great - I'm mildly surprised to see that we don't insist that any directed Path between two nodes of the same kind (e.g. Type
) decreases. Is that not possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do insist on that. See the next paragraph.
hugr/src/proptest.rs
Outdated
/// Consider the tree of values generated. Each node is labelled with a | ||
/// [RecursionDepth]. | ||
/// | ||
/// Consider a path between two different nodes of the same type(e.g. two [Type]s, or two |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Consider a path between two different nodes of the same type(e.g. two [Type]s, or two | |
/// Consider a path between two different nodes of the same kind (e.g. two [Type]s, or two |
} | ||
} | ||
|
||
impl<I: TryInto<usize>> From<I> for RecursionDepth |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you using this? Lovely bit of parametrization, but are we really? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are using it yes. Not strictly required but I think it's fine?
hugr/src/types.rs
Outdated
Sum( | ||
#[cfg_attr( | ||
all(test, feature = "proptest"), | ||
proptest(strategy = "any_with::<SumType>(params)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my understanding - presumably this whole #[cfg_attr....]
is necessary whenever you want to pass down the params
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well the cfg_attr
is needed to apply the proptest(strategy = ...
attribute conditionally. That attribute is needed because we are using non-default params
, see paragraph 4 https://proptest-rs.github.io/proptest/proptest-derive/modifiers.html#params
vec(any_with::<TypeArg>(depth.descend()), 0..3).boxed() | ||
}; | ||
(any_nonempty_string(), args, any::<ExtensionId>(), bound) | ||
.prop_map(|(id, args, extension, bound)| Self::new(id, args, extension, bound)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.prop_map(|(id, args, extension, bound)| Self::new(id, args, extension, bound)) | |
.prop_map(Self::new) |
or do you need the lambda to untuple?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice but we do need the lambda to untuple.
use ::proptest::prelude::*; | ||
|
||
#[derive(Default)] | ||
pub struct CustomTypeArbitraryParameters(RecursionDepth, Option<TypeBound>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I wondered whether you could remove this Option but the answer is no because what you are selecting is (not a TypeBound but) a strategy for generating TypeBounds, i.e. potentially any::<TypeBound>
.
hugr/Cargo.toml
Outdated
@@ -22,7 +22,9 @@ bench = false | |||
path = "src/lib.rs" | |||
|
|||
[features] | |||
default = ["proptest"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also pondering how well this agrees with what's in justfile
justfile
Outdated
"poetry run pytest" | ||
) | ||
|
||
# Run all proptests | ||
proptest: | ||
cargo test --feature proptest '::proptest' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
--feature proptest
is on by default, no? So the '::proptest'
here causes the tests to execute when cargo test
(/ cargo test --feature proptest
, which should be equivalent) does not?
I think keeping proptests non- |
I am going to remove the feature flag, for the reasons noted in #981 (comment). Do lmk if you have a problem with this. |
950679c
to
86e5e11
Compare
## 🤖 New release * `hugr`: 0.3.1 -> 0.4.0 (⚠️ API breaking changes) ###⚠️ `hugr` breaking changes ``` --- failure inherent_method_const_removed: pub method is no longer const --- Description: A publicly-visible method or associated fn is no longer `const` and can no longer be used in a `const` context. ref: https://doc.rust-lang.org/reference/const_eval.html impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.31.0/src/lints/inherent_method_const_removed.ron Failed in: ConstF64::new in /tmp/.tmpwo5blB/hugr/hugr/src/std_extensions/arithmetic/float_types.rs:43 --- failure struct_missing: pub struct removed or renamed --- Description: A publicly-visible struct cannot be imported by its prior path. A `pub use` may have been removed, or the struct itself may have been renamed or removed entirely. ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.31.0/src/lints/struct_missing.ron Failed in: struct hugr::ops::constant::ExtensionValue, previously in file /tmp/.tmpq1W6bC/hugr/src/ops/constant.rs:184 ``` <details><summary><i><b>Changelog</b></i></summary><p> <blockquote> ## 0.4.0 (2024-05-20) ### Bug Fixes - Disallow non-finite values for `ConstF64` ([#1075](#1075)) - Serialization round-trips ([#948](#948)) - [**breaking**] Combine `ConstIntU` and `ConstIntS` ([#974](#974)) - Disable serialisation tests when miri is active ([#977](#977)) - [**breaking**] Serialisation schema ([#968](#968)) - Correct constant fold for `fne`. ([#995](#995)) - [**breaking**] Serialisation fixes ([#997](#997)) - [**breaking**] OpDef serialisation ([#1013](#1013)) - NaryLogicOp constant folding ([#1026](#1026)) ### Features - Add verification to constant folding ([#1030](#1030)) - Add `Const::get_custom_value` ([#1037](#1037)) - Add serialization schema for metadata ([#1038](#1038)) - 'Replace' rewrite returns node map ([#929](#929)) - `new` methods for leaf ops ([#940](#940)) - Add `string` type and `print` function to `prelude` ([#942](#942)) - `CustomOp::extension` utility function ([#951](#951)) - [**breaking**] Add `non_exhaustive` to various enums ([#952](#952)) - Encoder metadata in serialized hugr ([#955](#955)) - [**breaking**] Bring back Value ([#967](#967)) - Add LoadFunction node ([#947](#947)) - Add From impls for TypeArg ([#1002](#1002)) - Constant-folding of integer and logic operations ([#1009](#1009)) - [**breaking**] Update serialisation schema, implement `CustomConst` serialisation ([#1005](#1005)) - Merge basic blocks algorithm ([#956](#956)) - [**breaking**] Allow panic operation to have any input and output wires ([#1024](#1024)) ### Refactor - [**breaking**] Rename `crate::ops::constant::ExtensionValue` => `OpaqueValue` ([#1036](#1036)) - Outline hugr::serialize::test ([#976](#976)) - [**breaking**] Replace SmolStr identifiers with wrapper types. ([#959](#959)) - Separate extension validation from the rest ([#1011](#1011)) - Remove "trait TypeParametrised" ([#1019](#1019)) ### Testing - Reorg OutlineCfg/nest_cfgs tests so hugr doesn't depend on algorithm ([#1007](#1007)) - Ignore tests which depend on typetag when cfg(miri) ([#1051](#1051)) - Really ignore tests which depend on typetag when cfg(miri) ([#1058](#1058)) - Proptests for round trip serialisation of `Type`s and `Op`s. ([#981](#981)) - Add a test of instantiating an extension set ([#939](#939)) - Ignore serialisation tests when using miri ([#975](#975)) - [**breaking**] Test roundtrip serialisation against strict + lax schema ([#982](#982)) - Fix some bad assert_matches ([#1006](#1006)) - Expand test of instantiating extension sets ([#1003](#1003)) - Fix unconnected ports in extension test ([#1010](#1010)) </blockquote> </p></details> --- This PR was generated with [release-plz](https://github.com/MarcoIeni/release-plz/). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Douglas Wilson <[email protected]>
Note that I make no attempt to generate "valid" types. In particular this means that type variables will be grossly inconsistent. It would be possible to improve this by passing a type environment through all the arbitrary instances in
Parameters
, although it's not clear to me just how far that would take us.We can't use
prop_recursive
for mutually recursive types, so I have reimplemented it usingParameters
.