Skip to content
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

Remove test-api-feature from chain-time #376

Conversation

danielSanchezQ
Copy link
Contributor

@danielSanchezQ danielSanchezQ commented May 11, 2020

Another module with support in the new chain-test-utils.

chain-test-utils/src/time/mod.rs Outdated Show resolved Hide resolved
chain-test-utils/src/time/mod.rs Outdated Show resolved Hide resolved
gen().into()
}

// Generate an `Epoch` given a `smoke::R` (random generator)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we now transitioning to smoke from quickcheck, which also has a framework for arbitrary, but reproducible stuff? Does it add value to use both?

I wish I was able to see what smoke can do from the documentation, but there's scarcely any.

Copy link
Contributor Author

@danielSanchezQ danielSanchezQ May 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the thing with quickcheck::Arbitrary is that since it is a trait it should be implemented in the same crate as the target type. So, the idea here is to have a "centralize" crate with the utilities to generate our inner types. That way that crate can be simply used in the tests without relying on features. We can still use quickcheck::Arbitrary for the self crate tests since it is useful, but rely on chain-test-utils for whatever is not in that crate.

About smoke, it just has some trait for generating types and abstracts some of the most basic generation of numbers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, the idea here is to have a "centralize" crate with the utilities to generate our inner types.

That might prove a bit more difficult to maintain in separation from the crates defining the types.

That way that crate can be simply used in the tests without relying on features.

What was the original problem we are trying to solve here? The "property-test-api" feature added the testing trait impls and possibly other test utilities for testing uses, and it was to be used in approximately this way:

# For non-test binaries.
# If the whole target is meant for testing, add "property-test-api" here as well
[dependencies]
chain-core = { path = "chain-deps/chain-core" }

# For cfg(test), integration tests under tests/, etc.
[dev-dependencies]
chain-core = { path = "chain-deps/chain-core", features = "property-test-api" }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mzabaluev the whole point of a generator based approach, is that it allow to define those generator elsewhere, since they are effectively just function/closures. A generator is just an "infinite" iterator of random value where the random engine is deterministically generated from a given seed, hence assuring reproducibility from a given Seed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm not explaining it properly, sorry.
With the property-test-api we are exporting the quickcheck dep. We are using this feature in the jormungandr tests (can't point out where exactly now). Which apparently is a misuse.

I see your point though. We could keep the feature for testing just the chain-deps. (But we may have the temptation in the future to use it just because it may be easier 🍔 )

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So there are two things:

  • we want a more flexible API than arbitrary to construct testing data;
  • there is a concern about test facilities embedded into the library crates and conditionally enabled, as being cumbersome to use, or easy to get wrong and pull in unnecessary dependencies.

Would the latter be a matter of convention that has been followed only loosely, which may be what is causing problems?

Another benefit of testing APIs built into crates is that you don't have to break encapsulation to provide the testing facilities: a public generator defined in a conditionally compiled testing submodule can freely use the type's internals to generate values, while we don't want to provide equivalent freedom to construct values in public API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another benefit of testing APIs built into crates is that you don't have to break encapsulation to provide the testing facilities: a public generator defined in a conditionally compiled testing submodule can freely use the type's internals to generate values, while we don't want to provide equivalent freedom to construct values in public API.

Yes, this is something that will happen for sure.

Well, we can keep both though. And careful use them where they are needed.

chain-test-utils/src/utils/mod.rs Outdated Show resolved Hide resolved
danielsanchezq added 2 commits May 11, 2020 15:23
Fix time era test (missing including end value)
@danielSanchezQ
Copy link
Contributor Author

I'm letting the feature-test-api as commented (bad branch name now). We can keep the testing utilities and see how to move in the future.


// `TimeEra` configuration, encapsulates the building boundaries for the inner data
#[derive(Clone)]
pub struct TimeEraGenCfg {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a bit less abbreviated: TimeEraGenConfig

Comment on lines 71 to 77
pub fn new(config: Option<TimeEraGenCfg>) -> Self {
Self { config }
}

pub fn with_config(&mut self, config: TimeEraGenCfg) {
self.config = Some(config);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These look a bit redundant: new could use the default configuration, so just be the same as the Default impl.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TimeEraGenCfg struct looks like it could provide a builder-style API, but maybe it's too much polish for the testing interface.

self.config = Some(config);
}

pub fn clear_config(&mut self) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need the mutability?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhm, for modifying the struct I think we need.

}

// Generate an `TimeEra` given a `smoke::R` (random generator) and a `TimeEraGenCfg` range limit tuple
pub fn generate_time_era_with_config(r: &mut R, config: &TimeEraGenCfg) -> TimeEra {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (as a public API) also looks redundant to the composition of TimeEraGenerator::with_config and the gen method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, my idea was that some times you will need a one shot time build and some times you will need a bunch of generated items. It feels overkill to build a generator just to get one object when you can just call a method.

Comment on lines +31 to +33
pub fn generate_slot(r: &mut R) -> Slot {
generate_slot_with(|| r.num())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this generate nonsensical slot values?
I'm trying to understand the purpose of non-bounded random generator methods here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it actually just makes sense if you want to test both acceptable values and not.


// Generator wrapper for TimeEra generator methods
pub struct TimeEraGenerator {
config: Option<TimeEraGenCfg>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather have a non-optional config and set range bounds to sensible defaults for a TimeEra.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where can I find some documentation about what would be some range bounds for a TimeEra?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. @vincenthz ?

I would like the elementary newtype values to be domain-valid by construction, unless their domain really covers the entire underlying integer type.

Copy link
Contributor

@NicolasDP NicolasDP left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add some documentation to the types and the functions. I am looking at what kind of type can be added and how they are suppose to be used/interact with each other.

Also ergonimically speaking, put the types at the top if possible. So we know what we are looking for and the impl at the bottom (before the tests mod though).

Comment on lines 71 to 73
pub fn new(config: Option<TimeEraGenCfg>) -> Self {
Self { config }
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the point to have a new with an optional of something and then a with_config to set the config?

use a Config and a ConfigBuilder if you need it. Use Default too. This will make your types more ergonomic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I was thinking that we may want to change the way the generator generates values mid-way on some computation and still keep the reproducible step from the same seed. I see that with_config was an unfortunate name for this. It should be a set_config.

@danielSanchezQ danielSanchezQ linked an issue May 12, 2020 that may be closed by this pull request
5 tasks
@danielSanchezQ
Copy link
Contributor Author

@NicolasDP , I expanded (and fixed) the documentation. But I'm not sure if it was in the way you intended. I think I did not understand properly what you meant about the types interaction.

@danielSanchezQ
Copy link
Contributor Author

I'm closing this for now. It will be resumed in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Generator based testing framework
4 participants