{"id":13566383,"url":"https://github.com/readysettech/proptest-stateful","last_synced_at":"2025-08-30T09:18:15.490Z","repository":{"id":168749845,"uuid":"644538290","full_name":"readysettech/proptest-stateful","owner":"readysettech","description":"Library for building stateful property tests using the proptest crate","archived":false,"fork":false,"pushed_at":"2025-08-15T22:03:44.000Z","size":38,"stargazers_count":25,"open_issues_count":7,"forks_count":0,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-08-29T11:53:58.520Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/readysettech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-05-23T18:20:51.000Z","updated_at":"2025-08-15T22:03:47.000Z","dependencies_parsed_at":"2024-05-22T17:47:56.052Z","dependency_job_id":"c9a18537-f97a-4615-93f4-295c31c96b7c","html_url":"https://github.com/readysettech/proptest-stateful","commit_stats":null,"previous_names":["readysettech/proptest-stateful"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/readysettech/proptest-stateful","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/readysettech%2Fproptest-stateful","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/readysettech%2Fproptest-stateful/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/readysettech%2Fproptest-stateful/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/readysettech%2Fproptest-stateful/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/readysettech","download_url":"https://codeload.github.com/readysettech/proptest-stateful/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/readysettech%2Fproptest-stateful/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272829732,"owners_count":25000289,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-30T02:00:09.474Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T13:02:08.464Z","updated_at":"2025-08-30T09:18:15.461Z","avatar_url":"https://github.com/readysettech.png","language":"Rust","funding_links":[],"categories":["Rust"],"sub_categories":[],"readme":"# proptest-stateful\n\n`proptest-stateful` is a Rust library created by\n[ReadySet](https://readyset.io/) for writing stateful property-based tests\nusing the\n[proptest](https://altsysrq.github.io/proptest-book/proptest/index.html) crate.\n\n[API Reference\nDocumentation](https://docs.rs/proptest-stateful/latest/proptest_stateful/).\n\n## Introduction\n\nProperty-based tests are often stateless: they generate an input, call a\nfunction, and check some property on the output. They do this without\nconsidering any underlying state of the system being tested.\n\n`proptest-stateful` extends this testing paradigm to stateful systems: stateful\nproperty tests generate a sequence of operations to run, executes them one at a\ntime, and checks postconditions for each operation along the way. If a test\nfails, `proptest-stateful` will attempt to remove individual steps in the test\nsequence to find a minimal failing case.\n\n`proptest-stateful` provides a trait for defining stateful property tests, with\ncallbacks for specifying things like generation strategies, model states,\npreconditions, and postconditions. Once these are defined, the\n`proptest-stateful` code can do the heavy lifting of generating valid test\ncases, running tests, and shrinking test failures.\n\n## Quickstart\n\nSuppose you want to test a simple `Counter` struct you've created:\n```rust\nstruct Counter {\n    count: usize,\n}\n\nimpl Counter {\n    fn new(count: usize) -\u003e Self {\n        Counter { count }\n    }\n\n    fn inc(\u0026mut self) {\n        self.count += 1;\n    }\n\n    fn dec(\u0026mut self) {\n        self.count -= 1;\n    }\n}\n```\nAt the start of a test case, we'll create a new `Counter`, and generate a\nsequence of `inc` and `dec` operations, so we first need to define an\n`Operation` type to represent these operations:\n```rust\n#[derive(Clone, Debug)]\nenum CounterOp {\n    Inc,\n    Dec,\n}\n```\nThe API requires us to define a state type, though for now it'll just be empty:\n```rust\n#[derive(Clone, Debug, Default)]\nstruct TestState {}\n```\nAnd we need a context type to hold runtime state when we're executing a test\ncase:\n```rust\nstruct TestContext {\n    counter: Counter,\n}\n```\nNow we just need to define callbacks to tell the framework how to generate and\nrun test cases. (We currently only support async test cases, so this is a\nlittle more complex than it needs to be since there's not really any need for\nasync in this test, but it still works fine.)\n```rust\n#[async_trait(?Send)]\nimpl ModelState for TestState {\n    type Operation = CounterOp;\n    type RunContext = TestContext;\n    type OperationStrategy = BoxedStrategy\u003cSelf::Operation\u003e;\n\n    fn op_generators(\u0026self) -\u003e Vec\u003cSelf::OperationStrategy\u003e {\n        // For each step test, arbitrarily pick Inc or Dec, regardless of the test state:\n        vec![Just(CounterOp::Inc).boxed(), Just(CounterOp::Dec).boxed()]\n    }\n\n    // No preconditions to worry about or test state to maintain yet\n    fn preconditions_met(\u0026self, _op: \u0026Self::Operation) -\u003e bool {\n        true\n    }\n    fn next_state(\u0026mut self, _op: \u0026Self::Operation) {}\n\n    async fn init_test_run(\u0026self) -\u003e Self::RunContext {\n        let counter = Counter::new(3); // Start with 3 to make the failing cases more interesting\n        TestContext { counter }\n    }\n\n    async fn run_op(\u0026self, op: \u0026Self::Operation, ctxt: \u0026mut Self::RunContext) {\n        match op {\n            CounterOp::Inc =\u003e ctxt.counter.inc(),\n            CounterOp::Dec =\u003e ctxt.counter.dec(),\n        }\n    }\n\n    async fn check_postconditions(\u0026self, _ctxt: \u0026mut Self::RunContext) {}\n    async fn clean_up_test_run(\u0026self, _ctxt: \u0026mut self::runcontext) {}\n}\n```\nFinally, you can run a test like so:\n```rust\n#[test]\nfn run_cases() {\n    let config = ProptestStatefulConfig {\n        min_ops: 10,\n        max_ops: 20,\n        test_case_timeout: Duration::from_secs(60),\n        proptest_config: ProptestConfig::default(),\n    };\n\n    proptest_stateful::test::\u003cTestState\u003e(config);\n}\n```\nIf you run this, you should quickly see a failure, because we didn't account\nfor underflow! If a test case causes the counter value to drop below 0, the\ntest will fail.  The test will then proceed to shrink the failing case, which\nwill bring you from a random-looking string of increment/decrement operations\nto this:\n```\nminimal failing input: [\n    Dec,\n    Dec,\n    Dec,\n    Dec,\n]\n```\n(Since we start with the counter at 3, we need to decrement 4 times to trigger\nan underflow.)\n\n### Fixing This Example\n\nLet's assume now that underflow is a known limitation, so we don't want to test\ncases that will trigger underflow panics. To do this, we need to maintain an\nactual model state of what we expect the current state of the counter to look\nlike:\n```rust\nstruct TestState {\n    model_count: usize,\n}\n\nimpl Default for TestState {\n    fn default() -\u003e Self {\n        TestState { model_count: 3 } // Set to match initial test value\n    }\n}\n```\nTo keep it up to date as we generate test steps, we implement `next_state`:\n```rust\n    fn next_state(\u0026mut self, op: \u0026Self::Operation) {\n        match op {\n            CounterOp::Inc =\u003e {\n                self.model_count += 1;\n            }\n            CounterOp::Dec =\u003e {\n                self.model_count -= 1;\n            }\n        }\n    }\n```\nAnd now we can use it for generators and preconditions:\n```rust\n    fn op_generators(\u0026self) -\u003e Vec\u003cSelf::OperationStrategy\u003e {\n        let mut ops = vec![Just(CounterOp::Inc).boxed()];\n        if self.model_count \u003e 0 {\n            ops.push(Just(CounterOp::Dec).boxed());\n        }\n        ops\n    }\n\n    fn preconditions_met(\u0026self, op: \u0026Self::Operation) -\u003e bool {\n        match op {\n            CounterOp::Inc =\u003e true,\n            CounterOp::Dec =\u003e self.model_count \u003e 0,\n        }\n    }\n```\nRunning this test should now pass, because it will never generate a test case\nthat drops the counter value below 0.\n\nYou can see the completed code and run this yourself via the `tests/counter.rs`\nfile.\n\nFor more complex real-world examples, check out these test suites we've written\nfor ReadySet:\n * [ddl_vertical.rs](https://github.com/readysettech/readyset/blob/main/replicators/tests/ddl_vertical.rs)\n * [vertical.rs](https://github.com/readysettech/readyset/blob/main/readyset-mysql/tests/vertical.rs)\n\n## Contributions\n\nWe welcome contributions! Check out [our issues\npage](https://github.com/readysettech/proptest-stateful/issues), and feel free\nto connect with us if you want to work on any of the outstanding tickets, or if\nyou have any other ideas for fixes or improvements that you'd like to share.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freadysettech%2Fproptest-stateful","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Freadysettech%2Fproptest-stateful","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freadysettech%2Fproptest-stateful/lists"}