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

Implement such-that maximum retries option #29

Merged
merged 5 commits into from
Jan 12, 2024

Conversation

jobtravaini
Copy link
Contributor

Context

clojure.test.check has the option to increase the maximum number of retries for generators. When creating complex schemas, you may face flakiness of generators and tests because of conditionals and other types of constraints. If the reader is interested in reading more about the context, the issue opened #28 has more context.

Changes

To enable configuration of max-retries on such-that.

Changes:

  • Overload the function schema-generators.generators/generator to allow a new argument named opts to be provided as options (enabling further extension required).
  • Change the CompositeGenerator protocol to accept opts as argument
  • Implement tests for maximum number of retries and guarantee the existing tests are working
  • Bump clojure to 1.11.1

Copy link
Member

@w01fe w01fe left a comment

Choose a reason for hiding this comment

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

Thanks for the PR! Left a few questions, please let me know your thoughts.

(composite-generator (s/spec s) params))))
params (merge {:subschema-generator gen :cache #?(:clj (java.util.IdentityHashMap.)
:cljs (atom {}))}
(or (options schema) {:max-retries 10}))]
Copy link
Member

Choose a reason for hiding this comment

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

This isn't quite right, you want to call options at the place where you have the schema you are trying to sample, which will typically be a subschema of schema.

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've made the change on 2a48605, is it what you have described?

Copy link
Member

Choose a reason for hiding this comment

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

Err not quite, sorry.

I think you want to store options in params as another key like subschema-options.

Then I'd define a helper function

(def max-retries [params schema] (((safe-get params :subschema-options) s) :max-retries 10))

and wherever you have

(get params :max-retries 10)

currently you'd use (max-retries params s) instead.

Does that make sense?

Copy link
Contributor Author

@jobtravaini jobtravaini Jan 11, 2024

Choose a reason for hiding this comment

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

I have tried what you have described. The (get params :max-retries 10) is part of the CompositeGenerator protocol implementation. Given this context, I have implemented the following code:

(defn max-retries
  [schema params]
  (((macros/safe-get params :subschema-options) schema) :max-retries 10))

(defprotocol CompositeGenerator
  (composite-generator [s params]))

(extend-protocol CompositeGenerator
  schema.spec.variant.VariantSpec
  (composite-generator [s params]
    (generators/such-that
     (fn [x]
       (let [pre (.-pre ^schema.spec.variant.VariantSpec s)
             post (.-post ^schema.spec.variant.VariantSpec s)]
         (not
          (or (pre x)
              (and post (post x))))))
     (generators/one-of
       (for [o (macros/safe-get s :options)]
         (if-let [g (:guard o)]
           (generators/such-that g (sub-generator o params) (max-retries s params))
           (sub-generator o params))))
     (max-retries s params)))

  ;; TODO: this does not currently capture proper semantics of maps with
  ;; both specific keys and key schemas that can override them.
  schema.spec.collection.CollectionSpec
  (composite-generator [s params]
    (generators/such-that
     (complement (.-pre ^schema.spec.collection.CollectionSpec s))
     (generators/fmap (:konstructor s) (elements-generator (:elements s) params))
     (max-retries s params)))

  schema.spec.leaf.LeafSpec
  (composite-generator [s _]
    (macros/assert! false "You must provide a leaf generator for %s" s)))

This implementation does not work because the s parameter received in the composite-generator function is (s/spec s) which provides me no longer a Schema, but a VariantSpec. Below you can check the options content on a runtime example:

{#schema.core.ConditionalSchema
 {:error-symbol nil
  :preds-and-schemas [[#<clojure.core$int_QMARK_@1d8946ca>
                       #<java.lang.Class@18b4aac2 class java.lang.String>]]} {:max-retries 100}}

The s parameter on this same example:

#schema.spec.variant.VariantSpec
{:err-f #<schema.core.ConditionalSchema$fn__6078@3641ebdc>
 :options ({:guard #<clojure.core$int_QMARK_@1d8946ca>
            :schema #<java.lang.Class@18b4aac2 class java.lang.String>})
 :post nil
 :pre #<schema.spec.core$_PLUS_no_precondition_PLUS_@4aafe9d4>}

I can change the implementation to perform the max-retries call on generator function level and use safe-get on the composite-generator to fetch the max-retries.

But I think it won't be much different from the current implementation.

Copy link
Member

Choose a reason for hiding this comment

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

Argh, my apologies, it's been too long and I forgot how this code worked, you had it right in 2a48605. Sorry again!


;; TODO: this does not currently capture proper semantics of maps with
;; both specific keys and key schemas that can override them.
schema.spec.collection.CollectionSpec
(composite-generator [s params]
(generators/such-that
(complement (.-pre ^schema.spec.collection.CollectionSpec s))
(generators/fmap (:konstructor s) (elements-generator (:elements s) params))))
(generators/fmap (:konstructor s) (elements-generator (:elements s) params))
(:max-retries params)))
Copy link
Member

Choose a reason for hiding this comment

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

If there's a default so this should always exist, can we safe-get please?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changes on 2a48605

Copy link
Member

@w01fe w01fe left a comment

Choose a reason for hiding this comment

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

Looking good, just a couple remaining minor comments. Thanks for the updates !

Copy link
Member

@w01fe w01fe left a comment

Choose a reason for hiding this comment

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

Looks great, thanks for the fix!

@w01fe w01fe merged commit e49cf6b into plumatic:master Jan 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants