From 12e18a1a2b1c3e2a1de10c30e01f8833549051ab Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Tue, 6 Feb 2024 21:38:43 +0000 Subject: [PATCH 01/23] Temp --- crates/apollo-smith/src/lib.rs | 1 + crates/apollo-smith/src/next/document.rs | 3 + crates/apollo-smith/src/next/existing.rs | 35 ++++++++++ crates/apollo-smith/src/next/invalid.rs | 20 ++++++ crates/apollo-smith/src/next/mod.rs | 55 ++++++++++++++++ .../mutations/add_object_type_definition.rs | 17 +++++ crates/apollo-smith/src/next/mutations/mod.rs | 15 +++++ .../src/next/mutations/remove_all_fields.rs | 17 +++++ crates/apollo-smith/src/next/unstructured.rs | 64 +++++++++++++++++++ crates/apollo-smith/src/next/valid.rs | 49 ++++++++++++++ fuzz/fuzz_targets/parser_next.rs | 47 ++++++++++++++ fuzz/src/lib.rs | 10 +++ 12 files changed, 333 insertions(+) create mode 100644 crates/apollo-smith/src/next/document.rs create mode 100644 crates/apollo-smith/src/next/existing.rs create mode 100644 crates/apollo-smith/src/next/invalid.rs create mode 100644 crates/apollo-smith/src/next/mod.rs create mode 100644 crates/apollo-smith/src/next/mutations/add_object_type_definition.rs create mode 100644 crates/apollo-smith/src/next/mutations/mod.rs create mode 100644 crates/apollo-smith/src/next/mutations/remove_all_fields.rs create mode 100644 crates/apollo-smith/src/next/unstructured.rs create mode 100644 crates/apollo-smith/src/next/valid.rs create mode 100644 fuzz/fuzz_targets/parser_next.rs diff --git a/crates/apollo-smith/src/lib.rs b/crates/apollo-smith/src/lib.rs index c72fa5874..e31222c60 100644 --- a/crates/apollo-smith/src/lib.rs +++ b/crates/apollo-smith/src/lib.rs @@ -11,6 +11,7 @@ pub(crate) mod input_object; pub(crate) mod input_value; pub(crate) mod interface; pub(crate) mod name; +mod next; pub(crate) mod object; pub(crate) mod operation; pub(crate) mod scalar; diff --git a/crates/apollo-smith/src/next/document.rs b/crates/apollo-smith/src/next/document.rs new file mode 100644 index 000000000..90bce2496 --- /dev/null +++ b/crates/apollo-smith/src/next/document.rs @@ -0,0 +1,3 @@ + + +trait diff --git a/crates/apollo-smith/src/next/existing.rs b/crates/apollo-smith/src/next/existing.rs new file mode 100644 index 000000000..a483280f3 --- /dev/null +++ b/crates/apollo-smith/src/next/existing.rs @@ -0,0 +1,35 @@ +use crate::next::unstructured::Unstructured; +use apollo_compiler::ast::{Name, Type}; +use std::ops::{Deref, DerefMut}; + +pub(crate) struct Existing<'u, 'ue, 'e>(pub &'e mut Unstructured<'u, 'ue>); +impl<'u, 'ue, 'e> Existing<'u, 'ue, 'e> { + pub(crate) fn type_name(&mut self) -> arbitrary::Result { + let names = self.schema().types.keys().cloned().collect::>(); + Ok(self.choose(&names)?.clone()) + } + + pub(crate) fn ty(&mut self) -> arbitrary::Result { + let idx = self.int_in_range(0..=3)?; + Ok(match idx { + 0 => Type::Named(self.existing().type_name()?.clone()), + 1 => Type::NonNullNamed(self.existing().type_name()?.clone()), + 2 => Type::List(Box::new(self.existing().ty()?)), + _ => Type::NonNullList(Box::new(self.existing().ty()?)), + }) + } +} + +impl<'u, 'ue, 'n> Deref for Existing<'u, 'ue, 'n> { + type Target = Unstructured<'u, 'ue>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'u, 'ue, 'n> DerefMut for Existing<'u, 'ue, 'n> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/crates/apollo-smith/src/next/invalid.rs b/crates/apollo-smith/src/next/invalid.rs new file mode 100644 index 000000000..3ac191438 --- /dev/null +++ b/crates/apollo-smith/src/next/invalid.rs @@ -0,0 +1,20 @@ +use crate::next::unstructured::Unstructured; +use apollo_compiler::ast::{Name, Type}; +use std::ops::{Deref, DerefMut}; + +pub(crate) struct Invalid<'u, 'ue, 'e>(pub &'e mut Unstructured<'u, 'ue>); +impl<'u, 'ue, 'e> Invalid<'u, 'ue, 'e> {} + +impl<'u, 'ue, 'n> Deref for Invalid<'u, 'ue, 'n> { + type Target = Unstructured<'u, 'ue>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'u, 'ue, 'n> DerefMut for Invalid<'u, 'ue, 'n> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/crates/apollo-smith/src/next/mod.rs b/crates/apollo-smith/src/next/mod.rs new file mode 100644 index 000000000..5ce0fd41f --- /dev/null +++ b/crates/apollo-smith/src/next/mod.rs @@ -0,0 +1,55 @@ +use arbitrary::Result; + +use crate::next::mutations::{all_mutations, Mutation}; +use crate::next::unstructured::Unstructured; +use apollo_compiler::ast::Document; + +mod existing; +mod invalid; +mod mutations; +mod unstructured; +mod valid; + +pub fn build_document(u: &mut arbitrary::Unstructured) -> Result<()> { + let mut doc = apollo_compiler::ast::Document::new(); + + let mut mutations = all_mutations(); + mutations.retain(|_| { + u.arbitrary() + .expect("fuzzer must be able to generate a bool") + }); + + let mut schema = apollo_compiler::Schema::builder() + .add_ast(&doc) + .build() + .expect("initial document must be valid"); + for _ in 0..1000 { + let (valid_mutation, was_applied) = + apply_mutation(&mut Unstructured::new(u, &schema), &mut doc, &mut mutations)?; + if was_applied { + match apollo_compiler::Schema::builder().add_ast(&doc).build() { + Ok(new_schema) if valid_mutation => schema = new_schema, + Ok(_new_schema) => { + panic!("valid schema returned from invalid mutation") + } + Err(_new_schema) if valid_mutation => { + panic!("invalid schema returned from valid mutation") + } + Err(_new_schema) => { + break; + } + } + } + } + Ok(()) +} + +fn apply_mutation( + u: &mut Unstructured, + doc: &mut Document, + mutations: &mut Vec>, +) -> Result<(bool, bool)> { + let mutation = &mutations[u.int_in_range(0..=mutations.len() - 1)?]; + let was_applied = mutation.apply(u, doc)?; + Ok((mutation.is_valid(), was_applied)) +} diff --git a/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs new file mode 100644 index 000000000..510810301 --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs @@ -0,0 +1,17 @@ +use crate::next::mutations::Mutation; +use crate::next::unstructured::Unstructured; +use apollo_compiler::ast::Document; + +pub(crate) struct AddObjectType; +impl Mutation for AddObjectType { + fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result { + doc.definitions + .push(apollo_compiler::ast::Definition::ObjectTypeDefinition( + u.valid().object_type_definition()?.into(), + )); + Ok(true) + } + fn is_valid(&self) -> bool { + true + } +} diff --git a/crates/apollo-smith/src/next/mutations/mod.rs b/crates/apollo-smith/src/next/mutations/mod.rs new file mode 100644 index 000000000..39ac49af8 --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/mod.rs @@ -0,0 +1,15 @@ +mod add_object_type_definition; +mod remove_all_fields; + +use crate::next::mutations::add_object_type_definition::AddObjectType; +use crate::next::unstructured::Unstructured; +use apollo_compiler::ast::Document; + +pub(crate) trait Mutation { + fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result; + fn is_valid(&self) -> bool; +} + +pub(crate) fn all_mutations() -> Vec> { + vec![Box::new(AddObjectType)] +} diff --git a/crates/apollo-smith/src/next/mutations/remove_all_fields.rs b/crates/apollo-smith/src/next/mutations/remove_all_fields.rs new file mode 100644 index 000000000..510810301 --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/remove_all_fields.rs @@ -0,0 +1,17 @@ +use crate::next::mutations::Mutation; +use crate::next::unstructured::Unstructured; +use apollo_compiler::ast::Document; + +pub(crate) struct AddObjectType; +impl Mutation for AddObjectType { + fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result { + doc.definitions + .push(apollo_compiler::ast::Definition::ObjectTypeDefinition( + u.valid().object_type_definition()?.into(), + )); + Ok(true) + } + fn is_valid(&self) -> bool { + true + } +} diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs new file mode 100644 index 000000000..40527ec34 --- /dev/null +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -0,0 +1,64 @@ +use crate::next::existing::Existing; +use crate::next::invalid::Invalid; +use crate::next::valid::Valid; +use apollo_compiler::ast::Name; +use apollo_compiler::NodeStr; +use arbitrary::Result; +use std::ops::{Deref, DerefMut}; + +pub(crate) struct Unstructured<'u, 'ue> { + pub(crate) u: &'ue mut arbitrary::Unstructured<'u>, + pub(crate) schema: &'ue apollo_compiler::Schema, +} + +impl Unstructured<'_, '_> { + pub(crate) fn new<'u, 'ue>( + u: &'ue mut arbitrary::Unstructured<'u>, + schema: &'ue apollo_compiler::Schema, + ) -> Unstructured<'u, 'ue> { + Unstructured { u, schema } + } +} + +impl<'u, 'ue> Deref for Unstructured<'u, 'ue> { + type Target = arbitrary::Unstructured<'u>; + + fn deref(&self) -> &Self::Target { + &self.u + } +} + +impl<'u, 'ue> DerefMut for Unstructured<'u, 'ue> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.u + } +} + +impl<'u, 'ue> Unstructured<'u, 'ue> { + pub(crate) fn valid(&mut self) -> Valid<'u, 'ue, '_> { + Valid(self) + } + + pub(crate) fn existing(&mut self) -> Existing<'u, 'ue, '_> { + Existing(self) + } + + pub(crate) fn invalid(&mut self) -> Invalid<'u, 'ue, '_> { + Invalid(self) + } + pub(crate) fn schema(&self) -> &apollo_compiler::Schema { + &self.schema + } + + pub(crate) fn arbitrary_node_str(&mut self) -> Result { + Ok(NodeStr::new(self.arbitrary()?)) + } + + pub(crate) fn arbitrary_name(&mut self) -> Result { + loop { + if let Ok(name) = Name::new(self.arbitrary_node_str()?) { + return Ok(name); + } + } + } +} diff --git a/crates/apollo-smith/src/next/valid.rs b/crates/apollo-smith/src/next/valid.rs new file mode 100644 index 000000000..3f55060ca --- /dev/null +++ b/crates/apollo-smith/src/next/valid.rs @@ -0,0 +1,49 @@ +use crate::next::unstructured::Unstructured; +use apollo_compiler::ast::{FieldDefinition, Name, ObjectTypeDefinition}; +use std::ops::{Deref, DerefMut}; + +pub(crate) struct Valid<'u, 'ue, 'n>(pub &'n mut Unstructured<'u, 'ue>); + +impl<'u, 'ue, 'n> Deref for Valid<'u, 'ue, 'n> { + type Target = Unstructured<'u, 'ue>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl<'u, 'ue, 'n> DerefMut for Valid<'u, 'ue, 'n> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'u, 'ue, 'n> Valid<'u, 'ue, 'n> { + pub(crate) fn object_type_definition(&'n mut self) -> arbitrary::Result { + Ok(ObjectTypeDefinition { + description: self.arbitrary_node_str()?.into(), + name: self.valid().type_name()?, + implements_interfaces: vec![], + directives: Default::default(), + fields: vec![self.valid().field_definition()?.into()], + }) + } + + pub(crate) fn type_name(&mut self) -> arbitrary::Result { + let existing_type_names = self.schema().types.keys().cloned().collect::>(); + loop { + let name = self.arbitrary_name()?; + if !existing_type_names.contains(&name) { + return Ok(name); + } + } + } + pub(crate) fn field_definition(&mut self) -> arbitrary::Result { + Ok(FieldDefinition { + description: self.arbitrary_node_str()?.into(), + name: self.arbitrary_name()?, + arguments: vec![], + ty: self.existing().ty()?, + directives: Default::default(), + }) + } +} diff --git a/fuzz/fuzz_targets/parser_next.rs b/fuzz/fuzz_targets/parser_next.rs new file mode 100644 index 000000000..54be45ce7 --- /dev/null +++ b/fuzz/fuzz_targets/parser_next.rs @@ -0,0 +1,47 @@ +#![no_main] +use apollo_parser::Parser; +use apollo_rs_fuzz::{generate_valid_document, log_gql_doc}; +use libfuzzer_sys::fuzz_target; +use log::debug; +use std::panic; + +fuzz_target!(|data: &[u8]| { + let _ = env_logger::try_init(); + + let doc_generated = match generate_valid_document(data) { + Ok(d) => d, + Err(_err) => { + return; + } + }; + + let parser = panic::catch_unwind(|| Parser::new(&doc_generated)); + + let parser = match parser { + Err(err) => { + panic!("error {err:?}"); + } + Ok(p) => p, + }; + debug!("======= DOCUMENT ======="); + debug!("{}", doc_generated); + debug!("========================"); + + let tree = parser.parse(); + // early return if the parser detected an error + let mut should_panic = false; + if tree.errors().len() > 0 { + should_panic = true; + let errors = tree + .errors() + .map(|err| err.message()) + .collect::>() + .join("\n"); + debug!("Parser errors ========== \n{:?}", errors); + debug!("========================"); + log_gql_doc(&doc_generated, &errors); + } + if should_panic { + panic!("error detected"); + } +}); diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs index 547643ed8..72bf20b50 100644 --- a/fuzz/src/lib.rs +++ b/fuzz/src/lib.rs @@ -12,6 +12,16 @@ pub fn generate_valid_document(input: &[u8]) -> Result { Ok(document.into()) } +pub fn generate_document(input: &[u8]) -> Result { + drop(env_logger::try_init()); + + let mut u = Unstructured::new(input); + let gql_doc = DocumentBuilder::new(&mut u)?; + let document = gql_doc.finish(); + + Ok(document.into()) +} + /// Log the error and the document generated for these errors /// Save it into files pub fn log_gql_doc(gql_doc: &str, errors: &str) { From 8fb7cbc792de43aedf2b74812edf63659b20b2b1 Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Wed, 7 Feb 2024 09:41:18 +0000 Subject: [PATCH 02/23] Temp --- crates/apollo-smith/src/next/document.rs | 41 ++++++++++++++++++- crates/apollo-smith/src/next/existing.rs | 3 +- crates/apollo-smith/src/next/invalid.rs | 2 +- crates/apollo-smith/src/next/mod.rs | 27 +++++++----- .../src/next/mutations/add_field.rs | 21 ++++++++++ crates/apollo-smith/src/next/mutations/mod.rs | 1 + .../src/next/mutations/remove_all_fields.rs | 12 +++--- crates/apollo-smith/src/next/unstructured.rs | 20 ++++++++- 8 files changed, 106 insertions(+), 21 deletions(-) create mode 100644 crates/apollo-smith/src/next/mutations/add_field.rs diff --git a/crates/apollo-smith/src/next/document.rs b/crates/apollo-smith/src/next/document.rs index 90bce2496..b77cf574e 100644 --- a/crates/apollo-smith/src/next/document.rs +++ b/crates/apollo-smith/src/next/document.rs @@ -1,3 +1,42 @@ +use crate::next::unstructured::Unstructured; +use apollo_compiler::ast::{Name, ObjectTypeDefinition, Type}; +use std::ops::{Deref, DerefMut}; +pub(crate) struct Document<'u, 'ue, 'd, 'ad> { + pub(crate) u: &'d mut Unstructured<'u, 'ue>, + pub(crate) doc: &'ad mut apollo_compiler::ast::Document, +} +impl<'u, 'ue, 'e, 'ad> Document<'u, 'ue, 'e, 'ad> { + pub(crate) fn with_object_type_definition( + &mut self, + callback: fn(&mut Unstructured, doc: &mut ObjectTypeDefinition) -> arbitrary::Result<()>, + ) -> arbitrary::Result<()> { + let mut definitions = self + .doc + .definitions + .iter_mut() + .filter_map(|def| match def { + apollo_compiler::ast::Definition::ObjectTypeDefinition(def) => Some(def.make_mut()), + _ => None, + }) + .collect::>(); -trait + let idx = self.u.choose_index(definitions.len())?; + + Ok(callback(self.u, definitions[idx])?) + } +} + +impl<'u, 'ue, 'd, 'ad> Deref for Document<'u, 'ue, 'd, 'ad> { + type Target = Unstructured<'u, 'ue>; + + fn deref(&self) -> &Self::Target { + &self.u + } +} + +impl<'u, 'ue, 'd, 'ad> DerefMut for Document<'u, 'ue, 'd, 'ad> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.u + } +} diff --git a/crates/apollo-smith/src/next/existing.rs b/crates/apollo-smith/src/next/existing.rs index a483280f3..a22a99749 100644 --- a/crates/apollo-smith/src/next/existing.rs +++ b/crates/apollo-smith/src/next/existing.rs @@ -2,10 +2,11 @@ use crate::next::unstructured::Unstructured; use apollo_compiler::ast::{Name, Type}; use std::ops::{Deref, DerefMut}; -pub(crate) struct Existing<'u, 'ue, 'e>(pub &'e mut Unstructured<'u, 'ue>); +pub(crate) struct Existing<'u, 'ue, 'e>(pub(crate) &'e mut Unstructured<'u, 'ue>); impl<'u, 'ue, 'e> Existing<'u, 'ue, 'e> { pub(crate) fn type_name(&mut self) -> arbitrary::Result { let names = self.schema().types.keys().cloned().collect::>(); + assert!(!names.is_empty()); Ok(self.choose(&names)?.clone()) } diff --git a/crates/apollo-smith/src/next/invalid.rs b/crates/apollo-smith/src/next/invalid.rs index 3ac191438..ba5b4f706 100644 --- a/crates/apollo-smith/src/next/invalid.rs +++ b/crates/apollo-smith/src/next/invalid.rs @@ -2,7 +2,7 @@ use crate::next::unstructured::Unstructured; use apollo_compiler::ast::{Name, Type}; use std::ops::{Deref, DerefMut}; -pub(crate) struct Invalid<'u, 'ue, 'e>(pub &'e mut Unstructured<'u, 'ue>); +pub(crate) struct Invalid<'u, 'ue, 'e>(pub(crate) &'e mut Unstructured<'u, 'ue>); impl<'u, 'ue, 'e> Invalid<'u, 'ue, 'e> {} impl<'u, 'ue, 'n> Deref for Invalid<'u, 'ue, 'n> { diff --git a/crates/apollo-smith/src/next/mod.rs b/crates/apollo-smith/src/next/mod.rs index 5ce0fd41f..b627db591 100644 --- a/crates/apollo-smith/src/next/mod.rs +++ b/crates/apollo-smith/src/next/mod.rs @@ -4,6 +4,7 @@ use crate::next::mutations::{all_mutations, Mutation}; use crate::next::unstructured::Unstructured; use apollo_compiler::ast::Document; +mod document; mod existing; mod invalid; mod mutations; @@ -18,14 +19,19 @@ pub fn build_document(u: &mut arbitrary::Unstructured) -> Result<()> { u.arbitrary() .expect("fuzzer must be able to generate a bool") }); + if mutations.is_empty() { + return Ok(()); + } let mut schema = apollo_compiler::Schema::builder() .add_ast(&doc) .build() .expect("initial document must be valid"); for _ in 0..1000 { - let (valid_mutation, was_applied) = - apply_mutation(&mut Unstructured::new(u, &schema), &mut doc, &mut mutations)?; + let u = &mut Unstructured::new(u, &schema); + let mutation = u.choose(&mut mutations)?; + let was_applied1 = mutation.apply(u, &mut doc)?; + let (valid_mutation, was_applied) = (mutation.is_valid(), was_applied1); if was_applied { match apollo_compiler::Schema::builder().add_ast(&doc).build() { Ok(new_schema) if valid_mutation => schema = new_schema, @@ -44,12 +50,13 @@ pub fn build_document(u: &mut arbitrary::Unstructured) -> Result<()> { Ok(()) } -fn apply_mutation( - u: &mut Unstructured, - doc: &mut Document, - mutations: &mut Vec>, -) -> Result<(bool, bool)> { - let mutation = &mutations[u.int_in_range(0..=mutations.len() - 1)?]; - let was_applied = mutation.apply(u, doc)?; - Ok((mutation.is_valid(), was_applied)) +#[cfg(test)] +mod test { + #[test] + fn test() { + let mut u = arbitrary::Unstructured::new(&[0; 32]); + if let Err(e) = super::build_document(&mut u) { + panic!("error: {:?}", e); + }; + } } diff --git a/crates/apollo-smith/src/next/mutations/add_field.rs b/crates/apollo-smith/src/next/mutations/add_field.rs new file mode 100644 index 000000000..d53e25b86 --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/add_field.rs @@ -0,0 +1,21 @@ +use crate::next::mutations::Mutation; +use crate::next::unstructured::Unstructured; +use apollo_compiler::ast::Document; +use apollo_compiler::Node; + +pub(crate) struct AddField; +impl Mutation for AddField { + fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result { + u.document(doc).with_object_type_definition(|u, o| { + let mut field_definition = u.valid().field_definition()?; + let existing_fields = o.fields.iter().map(|f| &f.name).collect::>(); + field_definition.name = u.arbitrary_unique_name(&existing_fields)?; + o.fields.push(Node::new(u.valid().field_definition()?)); + Ok(()) + })?; + Ok(true) + } + fn is_valid(&self) -> bool { + true + } +} diff --git a/crates/apollo-smith/src/next/mutations/mod.rs b/crates/apollo-smith/src/next/mutations/mod.rs index 39ac49af8..8eb609b0d 100644 --- a/crates/apollo-smith/src/next/mutations/mod.rs +++ b/crates/apollo-smith/src/next/mutations/mod.rs @@ -1,3 +1,4 @@ +mod add_field; mod add_object_type_definition; mod remove_all_fields; diff --git a/crates/apollo-smith/src/next/mutations/remove_all_fields.rs b/crates/apollo-smith/src/next/mutations/remove_all_fields.rs index 510810301..5dab3f078 100644 --- a/crates/apollo-smith/src/next/mutations/remove_all_fields.rs +++ b/crates/apollo-smith/src/next/mutations/remove_all_fields.rs @@ -2,16 +2,14 @@ use crate::next::mutations::Mutation; use crate::next::unstructured::Unstructured; use apollo_compiler::ast::Document; -pub(crate) struct AddObjectType; -impl Mutation for AddObjectType { +pub(crate) struct RemoveAllFields; +impl Mutation for RemoveAllFields { fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result { - doc.definitions - .push(apollo_compiler::ast::Definition::ObjectTypeDefinition( - u.valid().object_type_definition()?.into(), - )); + u.document(doc) + .with_object_type_definition(|u, o| Ok(o.fields.clear()))?; Ok(true) } fn is_valid(&self) -> bool { - true + false } } diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index 40527ec34..d0d3dcdbb 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -1,3 +1,4 @@ +use crate::next::document::Document; use crate::next::existing::Existing; use crate::next::invalid::Invalid; use crate::next::valid::Valid; @@ -42,10 +43,17 @@ impl<'u, 'ue> Unstructured<'u, 'ue> { pub(crate) fn existing(&mut self) -> Existing<'u, 'ue, '_> { Existing(self) } - pub(crate) fn invalid(&mut self) -> Invalid<'u, 'ue, '_> { Invalid(self) } + + pub(crate) fn document<'d, 'ad>( + &mut self, + doc: &'ad mut apollo_compiler::ast::Document, + ) -> Document<'u, 'ue, '_, 'ad> { + Document { u: self, doc } + } + pub(crate) fn schema(&self) -> &apollo_compiler::Schema { &self.schema } @@ -61,4 +69,14 @@ impl<'u, 'ue> Unstructured<'u, 'ue> { } } } + + pub(crate) fn arbitrary_unique_name(&mut self, existing: &Vec<&Name>) -> Result { + loop { + if let Ok(name) = self.arbitrary_name() { + if !existing.contains(&&name) { + return Ok(name); + } + } + } + } } From 844af928e0b69877397210f5252dc611b699dcfa Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Wed, 7 Feb 2024 09:44:29 +0000 Subject: [PATCH 03/23] Temp --- crates/apollo-smith/src/next/document.rs | 2 +- crates/apollo-smith/src/next/invalid.rs | 1 - crates/apollo-smith/src/next/mod.rs | 11 ++++------- crates/apollo-smith/src/next/mutations/add_field.rs | 4 ++-- .../src/next/mutations/add_object_type_definition.rs | 4 ++-- crates/apollo-smith/src/next/mutations/mod.rs | 2 +- .../src/next/mutations/remove_all_fields.rs | 6 +++--- 7 files changed, 13 insertions(+), 17 deletions(-) diff --git a/crates/apollo-smith/src/next/document.rs b/crates/apollo-smith/src/next/document.rs index b77cf574e..30f7a2c4a 100644 --- a/crates/apollo-smith/src/next/document.rs +++ b/crates/apollo-smith/src/next/document.rs @@ -1,5 +1,5 @@ use crate::next::unstructured::Unstructured; -use apollo_compiler::ast::{Name, ObjectTypeDefinition, Type}; +use apollo_compiler::ast::ObjectTypeDefinition; use std::ops::{Deref, DerefMut}; pub(crate) struct Document<'u, 'ue, 'd, 'ad> { diff --git a/crates/apollo-smith/src/next/invalid.rs b/crates/apollo-smith/src/next/invalid.rs index ba5b4f706..2b3a3f375 100644 --- a/crates/apollo-smith/src/next/invalid.rs +++ b/crates/apollo-smith/src/next/invalid.rs @@ -1,5 +1,4 @@ use crate::next::unstructured::Unstructured; -use apollo_compiler::ast::{Name, Type}; use std::ops::{Deref, DerefMut}; pub(crate) struct Invalid<'u, 'ue, 'e>(pub(crate) &'e mut Unstructured<'u, 'ue>); diff --git a/crates/apollo-smith/src/next/mod.rs b/crates/apollo-smith/src/next/mod.rs index b627db591..5644fe454 100644 --- a/crates/apollo-smith/src/next/mod.rs +++ b/crates/apollo-smith/src/next/mod.rs @@ -1,8 +1,7 @@ use arbitrary::Result; -use crate::next::mutations::{all_mutations, Mutation}; +use crate::next::mutations::all_mutations; use crate::next::unstructured::Unstructured; -use apollo_compiler::ast::Document; mod document; mod existing; @@ -30,15 +29,13 @@ pub fn build_document(u: &mut arbitrary::Unstructured) -> Result<()> { for _ in 0..1000 { let u = &mut Unstructured::new(u, &schema); let mutation = u.choose(&mut mutations)?; - let was_applied1 = mutation.apply(u, &mut doc)?; - let (valid_mutation, was_applied) = (mutation.is_valid(), was_applied1); - if was_applied { + if mutation.apply(u, &mut doc).is_ok() { match apollo_compiler::Schema::builder().add_ast(&doc).build() { - Ok(new_schema) if valid_mutation => schema = new_schema, + Ok(new_schema) if mutation.is_valid() => schema = new_schema, Ok(_new_schema) => { panic!("valid schema returned from invalid mutation") } - Err(_new_schema) if valid_mutation => { + Err(_new_schema) if mutation.is_valid() => { panic!("invalid schema returned from valid mutation") } Err(_new_schema) => { diff --git a/crates/apollo-smith/src/next/mutations/add_field.rs b/crates/apollo-smith/src/next/mutations/add_field.rs index d53e25b86..f305ba532 100644 --- a/crates/apollo-smith/src/next/mutations/add_field.rs +++ b/crates/apollo-smith/src/next/mutations/add_field.rs @@ -5,7 +5,7 @@ use apollo_compiler::Node; pub(crate) struct AddField; impl Mutation for AddField { - fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result { + fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result<()> { u.document(doc).with_object_type_definition(|u, o| { let mut field_definition = u.valid().field_definition()?; let existing_fields = o.fields.iter().map(|f| &f.name).collect::>(); @@ -13,7 +13,7 @@ impl Mutation for AddField { o.fields.push(Node::new(u.valid().field_definition()?)); Ok(()) })?; - Ok(true) + Ok(()) } fn is_valid(&self) -> bool { true diff --git a/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs index 510810301..e153c2e28 100644 --- a/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs @@ -4,12 +4,12 @@ use apollo_compiler::ast::Document; pub(crate) struct AddObjectType; impl Mutation for AddObjectType { - fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result { + fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result<()> { doc.definitions .push(apollo_compiler::ast::Definition::ObjectTypeDefinition( u.valid().object_type_definition()?.into(), )); - Ok(true) + Ok(()) } fn is_valid(&self) -> bool { true diff --git a/crates/apollo-smith/src/next/mutations/mod.rs b/crates/apollo-smith/src/next/mutations/mod.rs index 8eb609b0d..37a15a7b6 100644 --- a/crates/apollo-smith/src/next/mutations/mod.rs +++ b/crates/apollo-smith/src/next/mutations/mod.rs @@ -7,7 +7,7 @@ use crate::next::unstructured::Unstructured; use apollo_compiler::ast::Document; pub(crate) trait Mutation { - fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result; + fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result<()>; fn is_valid(&self) -> bool; } diff --git a/crates/apollo-smith/src/next/mutations/remove_all_fields.rs b/crates/apollo-smith/src/next/mutations/remove_all_fields.rs index 5dab3f078..eedc6f5e5 100644 --- a/crates/apollo-smith/src/next/mutations/remove_all_fields.rs +++ b/crates/apollo-smith/src/next/mutations/remove_all_fields.rs @@ -4,10 +4,10 @@ use apollo_compiler::ast::Document; pub(crate) struct RemoveAllFields; impl Mutation for RemoveAllFields { - fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result { + fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result<()> { u.document(doc) - .with_object_type_definition(|u, o| Ok(o.fields.clear()))?; - Ok(true) + .with_object_type_definition(|_u, o| Ok(o.fields.clear()))?; + Ok(()) } fn is_valid(&self) -> bool { false From a7c78f901691d884fd8fd82a8b7aab1f8c02e763 Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Wed, 7 Feb 2024 10:42:17 +0000 Subject: [PATCH 04/23] Temp --- crates/apollo-smith/src/next/existing.rs | 9 ++------ .../src/next/mutations/add_field.rs | 4 ++-- .../src/next/mutations/add_self_field.rs | 23 +++++++++++++++++++ crates/apollo-smith/src/next/mutations/mod.rs | 1 + crates/apollo-smith/src/next/unstructured.rs | 19 ++++++++++++++- 5 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 crates/apollo-smith/src/next/mutations/add_self_field.rs diff --git a/crates/apollo-smith/src/next/existing.rs b/crates/apollo-smith/src/next/existing.rs index a22a99749..bcc4e7cc3 100644 --- a/crates/apollo-smith/src/next/existing.rs +++ b/crates/apollo-smith/src/next/existing.rs @@ -11,13 +11,8 @@ impl<'u, 'ue, 'e> Existing<'u, 'ue, 'e> { } pub(crate) fn ty(&mut self) -> arbitrary::Result { - let idx = self.int_in_range(0..=3)?; - Ok(match idx { - 0 => Type::Named(self.existing().type_name()?.clone()), - 1 => Type::NonNullNamed(self.existing().type_name()?.clone()), - 2 => Type::List(Box::new(self.existing().ty()?)), - _ => Type::NonNullList(Box::new(self.existing().ty()?)), - }) + let name = self.type_name()?; + self.wrap_ty(name) } } diff --git a/crates/apollo-smith/src/next/mutations/add_field.rs b/crates/apollo-smith/src/next/mutations/add_field.rs index f305ba532..75bbbcfc2 100644 --- a/crates/apollo-smith/src/next/mutations/add_field.rs +++ b/crates/apollo-smith/src/next/mutations/add_field.rs @@ -8,8 +8,8 @@ impl Mutation for AddField { fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result<()> { u.document(doc).with_object_type_definition(|u, o| { let mut field_definition = u.valid().field_definition()?; - let existing_fields = o.fields.iter().map(|f| &f.name).collect::>(); - field_definition.name = u.arbitrary_unique_name(&existing_fields)?; + let existing_field_names = o.fields.iter().map(|f| &f.name).collect::>(); + field_definition.name = u.arbitrary_unique_name(&existing_field_names)?; o.fields.push(Node::new(u.valid().field_definition()?)); Ok(()) })?; diff --git a/crates/apollo-smith/src/next/mutations/add_self_field.rs b/crates/apollo-smith/src/next/mutations/add_self_field.rs new file mode 100644 index 000000000..faf7ce99d --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/add_self_field.rs @@ -0,0 +1,23 @@ +use crate::next::mutations::Mutation; +use crate::next::unstructured::Unstructured; +use apollo_compiler::ast::Document; +use apollo_compiler::Node; + +pub(crate) struct AddField; +impl Mutation for AddField { + fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result<()> { + u.document(doc).with_object_type_definition(|u, o| { + let type_name = o.name.clone(); + let mut field_definition = u.valid().field_definition()?; + let existing_field_names = o.fields.iter().map(|f| &f.name).collect::>(); + field_definition.name = u.arbitrary_unique_name(&existing_field_names)?; + field_definition.ty = u.wrap_ty(type_name)?; + o.fields.push(Node::new(u.valid().field_definition()?)); + Ok(()) + })?; + Ok(()) + } + fn is_valid(&self) -> bool { + true + } +} diff --git a/crates/apollo-smith/src/next/mutations/mod.rs b/crates/apollo-smith/src/next/mutations/mod.rs index 37a15a7b6..c452369ea 100644 --- a/crates/apollo-smith/src/next/mutations/mod.rs +++ b/crates/apollo-smith/src/next/mutations/mod.rs @@ -1,5 +1,6 @@ mod add_field; mod add_object_type_definition; +mod add_self_field; mod remove_all_fields; use crate::next::mutations::add_object_type_definition::AddObjectType; diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index d0d3dcdbb..6265c9d33 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -2,7 +2,7 @@ use crate::next::document::Document; use crate::next::existing::Existing; use crate::next::invalid::Invalid; use crate::next::valid::Valid; -use apollo_compiler::ast::Name; +use apollo_compiler::ast::{Name, Type}; use apollo_compiler::NodeStr; use arbitrary::Result; use std::ops::{Deref, DerefMut}; @@ -79,4 +79,21 @@ impl<'u, 'ue> Unstructured<'u, 'ue> { } } } + + pub(crate) fn wrap_ty(&mut self, name: Name) -> Result { + let depth = self.int_in_range(0..=10)?; + let mut ty = if self.arbitrary()? { + Type::Named(name.clone()) + } else { + Type::NonNullNamed(name.clone()) + }; + for _ in 0..depth { + if self.arbitrary()? { + ty = Type::List(Box::new(ty)) + } else { + ty = Type::NonNullList(Box::new(ty)) + } + } + Ok(ty) + } } From f0b5c3a7d97815eb4b90faa1ffaf33e9f20e91d6 Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Wed, 7 Feb 2024 11:41:18 +0000 Subject: [PATCH 05/23] Temp --- crates/apollo-smith/src/next/document.rs | 23 +++++++++++++++++-- ...add_directive_to_object_type_definition.rs | 21 +++++++++++++++++ crates/apollo-smith/src/next/mutations/mod.rs | 1 + 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 crates/apollo-smith/src/next/mutations/add_directive_to_object_type_definition.rs diff --git a/crates/apollo-smith/src/next/document.rs b/crates/apollo-smith/src/next/document.rs index 30f7a2c4a..37df558b2 100644 --- a/crates/apollo-smith/src/next/document.rs +++ b/crates/apollo-smith/src/next/document.rs @@ -1,5 +1,5 @@ use crate::next::unstructured::Unstructured; -use apollo_compiler::ast::ObjectTypeDefinition; +use apollo_compiler::ast::{FieldDefinition, ObjectTypeDefinition}; use std::ops::{Deref, DerefMut}; pub(crate) struct Document<'u, 'ue, 'd, 'ad> { @@ -9,7 +9,26 @@ pub(crate) struct Document<'u, 'ue, 'd, 'ad> { impl<'u, 'ue, 'e, 'ad> Document<'u, 'ue, 'e, 'ad> { pub(crate) fn with_object_type_definition( &mut self, - callback: fn(&mut Unstructured, doc: &mut ObjectTypeDefinition) -> arbitrary::Result<()>, + callback: fn(&mut Unstructured, ty: &mut ObjectTypeDefinition) -> arbitrary::Result<()>, + ) -> arbitrary::Result<()> { + let mut definitions = self + .doc + .definitions + .iter_mut() + .filter_map(|def| match def { + apollo_compiler::ast::Definition::ObjectTypeDefinition(def) => Some(def.make_mut()), + _ => None, + }) + .collect::>(); + + let idx = self.u.choose_index(definitions.len())?; + + Ok(callback(self.u, definitions[idx])?) + } + + pub(crate) fn with_field_definition( + &mut self, + callback: fn(&mut Unstructured, doc: &mut FieldDefinition) -> arbitrary::Result<()>, ) -> arbitrary::Result<()> { let mut definitions = self .doc diff --git a/crates/apollo-smith/src/next/mutations/add_directive_to_object_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_directive_to_object_type_definition.rs new file mode 100644 index 000000000..75bbbcfc2 --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/add_directive_to_object_type_definition.rs @@ -0,0 +1,21 @@ +use crate::next::mutations::Mutation; +use crate::next::unstructured::Unstructured; +use apollo_compiler::ast::Document; +use apollo_compiler::Node; + +pub(crate) struct AddField; +impl Mutation for AddField { + fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result<()> { + u.document(doc).with_object_type_definition(|u, o| { + let mut field_definition = u.valid().field_definition()?; + let existing_field_names = o.fields.iter().map(|f| &f.name).collect::>(); + field_definition.name = u.arbitrary_unique_name(&existing_field_names)?; + o.fields.push(Node::new(u.valid().field_definition()?)); + Ok(()) + })?; + Ok(()) + } + fn is_valid(&self) -> bool { + true + } +} diff --git a/crates/apollo-smith/src/next/mutations/mod.rs b/crates/apollo-smith/src/next/mutations/mod.rs index c452369ea..57854169f 100644 --- a/crates/apollo-smith/src/next/mutations/mod.rs +++ b/crates/apollo-smith/src/next/mutations/mod.rs @@ -1,3 +1,4 @@ +mod add_directive_to_object_type_definition; mod add_field; mod add_object_type_definition; mod add_self_field; From 0b53da78a54161fe1037471cef061843b4d9ea8d Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Wed, 14 Feb 2024 08:47:42 +0000 Subject: [PATCH 06/23] Temp --- crates/apollo-smith/Cargo.toml | 2 + crates/apollo-smith/src/lib.rs | 2 +- .../apollo-smith/src/next/ast/definition.rs | 83 +++ .../src/next/ast/directive_definition.rs | 74 +++ crates/apollo-smith/src/next/ast/document.rs | 489 ++++++++++++++++++ crates/apollo-smith/src/next/ast/mod.rs | 4 + .../src/next/ast/object_type_definition.rs | 24 + crates/apollo-smith/src/next/common.rs | 75 +++ crates/apollo-smith/src/next/document.rs | 61 --- crates/apollo-smith/src/next/existing.rs | 31 -- crates/apollo-smith/src/next/invalid.rs | 19 - crates/apollo-smith/src/next/mod.rs | 170 ++++-- .../mutations/add_directive_definition.rs | 27 + ...add_directive_to_object_type_definition.rs | 21 - .../mutations/add_enum_type_definition.rs | 27 + .../src/next/mutations/add_field.rs | 21 - .../add_input_object_type_definition.rs | 27 + .../add_interface_type_definition.rs | 27 + .../mutations/add_object_type_definition.rs | 26 +- .../next/mutations/add_schema_definition.rs | 27 + .../src/next/mutations/add_self_field.rs | 23 - .../mutations/add_union_type_definition.rs | 27 + crates/apollo-smith/src/next/mutations/mod.rs | 46 +- .../src/next/mutations/remove_all_fields.rs | 18 +- .../src/next/schema/extended_type.rs | 54 ++ crates/apollo-smith/src/next/schema/mod.rs | 2 + crates/apollo-smith/src/next/schema/schema.rs | 131 +++++ crates/apollo-smith/src/next/test.graphql | 7 + crates/apollo-smith/src/next/unstructured.rs | 118 ++--- crates/apollo-smith/src/next/valid.rs | 49 -- fuzz/Cargo.toml | 6 + fuzz/fuzz_targets/parser_next.rs | 10 +- fuzz/src/lib.rs | 11 +- 33 files changed, 1344 insertions(+), 395 deletions(-) create mode 100644 crates/apollo-smith/src/next/ast/definition.rs create mode 100644 crates/apollo-smith/src/next/ast/directive_definition.rs create mode 100644 crates/apollo-smith/src/next/ast/document.rs create mode 100644 crates/apollo-smith/src/next/ast/mod.rs create mode 100644 crates/apollo-smith/src/next/ast/object_type_definition.rs create mode 100644 crates/apollo-smith/src/next/common.rs delete mode 100644 crates/apollo-smith/src/next/document.rs delete mode 100644 crates/apollo-smith/src/next/existing.rs delete mode 100644 crates/apollo-smith/src/next/invalid.rs create mode 100644 crates/apollo-smith/src/next/mutations/add_directive_definition.rs delete mode 100644 crates/apollo-smith/src/next/mutations/add_directive_to_object_type_definition.rs create mode 100644 crates/apollo-smith/src/next/mutations/add_enum_type_definition.rs delete mode 100644 crates/apollo-smith/src/next/mutations/add_field.rs create mode 100644 crates/apollo-smith/src/next/mutations/add_input_object_type_definition.rs create mode 100644 crates/apollo-smith/src/next/mutations/add_interface_type_definition.rs create mode 100644 crates/apollo-smith/src/next/mutations/add_schema_definition.rs delete mode 100644 crates/apollo-smith/src/next/mutations/add_self_field.rs create mode 100644 crates/apollo-smith/src/next/mutations/add_union_type_definition.rs create mode 100644 crates/apollo-smith/src/next/schema/extended_type.rs create mode 100644 crates/apollo-smith/src/next/schema/mod.rs create mode 100644 crates/apollo-smith/src/next/schema/schema.rs create mode 100644 crates/apollo-smith/src/next/test.graphql delete mode 100644 crates/apollo-smith/src/next/valid.rs diff --git a/crates/apollo-smith/Cargo.toml b/crates/apollo-smith/Cargo.toml index 13f8d60bf..01588c47a 100644 --- a/crates/apollo-smith/Cargo.toml +++ b/crates/apollo-smith/Cargo.toml @@ -28,6 +28,8 @@ arbitrary = { version = "1.3.0", features = ["derive"] } indexmap = "2.0.0" once_cell = "1.9.0" thiserror = "1.0.37" +paste = "1.0.0" +uuid = { version = "1.7.0", features = ["v4"] } [dev-dependencies] expect-test = "1.4" diff --git a/crates/apollo-smith/src/lib.rs b/crates/apollo-smith/src/lib.rs index e31222c60..ee1573639 100644 --- a/crates/apollo-smith/src/lib.rs +++ b/crates/apollo-smith/src/lib.rs @@ -11,7 +11,7 @@ pub(crate) mod input_object; pub(crate) mod input_value; pub(crate) mod interface; pub(crate) mod name; -mod next; +pub mod next; pub(crate) mod object; pub(crate) mod operation; pub(crate) mod scalar; diff --git a/crates/apollo-smith/src/next/ast/definition.rs b/crates/apollo-smith/src/next/ast/definition.rs new file mode 100644 index 000000000..33901500d --- /dev/null +++ b/crates/apollo-smith/src/next/ast/definition.rs @@ -0,0 +1,83 @@ +use apollo_compiler::ast::{Definition, InputObjectTypeDefinition, Name, Type}; +use apollo_compiler::schema::ExtendedType; +use arbitrary::Unstructured; + +pub(crate) trait DefinitionExt { + fn ty(&self, u: &mut Unstructured) -> arbitrary::Result; +} + +impl DefinitionExt for Definition { + fn ty(&self, u: &mut Unstructured) -> arbitrary::Result { + let name = self.name().expect("definition must have a name").clone(); + Ok(ty(u, name)?) + } +} + +impl DefinitionExt for InputObjectTypeDefinition { + fn ty(&self, u: &mut Unstructured) -> arbitrary::Result { + Ok(ty(u, self.name.clone())?) + } +} + +fn ty(u: &mut Unstructured, name: Name) -> arbitrary::Result { + let mut ty = if u.arbitrary()? { + Type::Named(name) + } else { + Type::NonNullNamed(name) + }; + + for _ in 0..u.int_in_range(0..=5)? { + if u.arbitrary()? { + ty = Type::List(Box::new(ty)) + } else { + ty = Type::NonNullList(Box::new(ty)) + }; + } + Ok(ty) +} + +#[derive(Debug)] +pub(crate) enum DefinitionKind { + OperationDefinition, + FragmentDefinition, + DirectiveDefinition, + SchemaDefinition, + ScalarTypeDefinition, + ObjectTypeDefinition, + InterfaceTypeDefinition, + UnionTypeDefinition, + EnumTypeDefinition, + InputObjectTypeDefinition, + SchemaExtension, + ScalarTypeExtension, + ObjectTypeExtension, + InterfaceTypeExtension, + UnionTypeExtension, + EnumTypeExtension, + InputObjectTypeExtension, +} + +impl DefinitionKind { + pub(crate) fn matches(&self, definition: &Definition) -> bool { + match (self, definition) { + (Self::OperationDefinition, Definition::OperationDefinition(_)) => true, + (Self::FragmentDefinition, Definition::FragmentDefinition(_)) => true, + (Self::DirectiveDefinition, Definition::DirectiveDefinition(_)) => true, + (Self::SchemaDefinition, Definition::SchemaDefinition(_)) => true, + (Self::ScalarTypeDefinition, Definition::ScalarTypeDefinition(_)) => true, + (Self::ObjectTypeDefinition, Definition::ObjectTypeDefinition(_)) => true, + (Self::InterfaceTypeDefinition, Definition::InterfaceTypeDefinition(_)) => true, + (Self::UnionTypeDefinition, Definition::UnionTypeDefinition(_)) => true, + (Self::EnumTypeDefinition, Definition::EnumTypeDefinition(_)) => true, + (Self::InputObjectTypeDefinition, Definition::InputObjectTypeDefinition(_)) => true, + (Self::SchemaExtension, Definition::SchemaExtension(_)) => true, + (Self::ScalarTypeExtension, Definition::ScalarTypeExtension(_)) => true, + (Self::ObjectTypeExtension, Definition::ObjectTypeExtension(_)) => true, + (Self::InterfaceTypeExtension, Definition::InterfaceTypeExtension(_)) => true, + (Self::UnionTypeExtension, Definition::UnionTypeExtension(_)) => true, + (Self::EnumTypeExtension, Definition::EnumTypeExtension(_)) => true, + (Self::InputObjectTypeExtension, Definition::InputObjectTypeExtension(_)) => true, + _ => false, + } + } +} diff --git a/crates/apollo-smith/src/next/ast/directive_definition.rs b/crates/apollo-smith/src/next/ast/directive_definition.rs new file mode 100644 index 000000000..48f25e972 --- /dev/null +++ b/crates/apollo-smith/src/next/ast/directive_definition.rs @@ -0,0 +1,74 @@ +use super::document::DocumentExt; +use apollo_compiler::ast::{ + Argument, Directive, DirectiveDefinition, DirectiveList, DirectiveLocation, Document, +}; +use apollo_compiler::{Node, Schema}; +use arbitrary::Unstructured; +use std::ops::Deref; + +pub(crate) struct LocationFilter(I, DirectiveLocation); + +impl<'a, T> Iterator for LocationFilter +where + T: Iterator>, +{ + type Item = &'a Node; + + fn next(&mut self) -> Option { + self.0.find(|d| d.locations.contains(&self.1)) + } +} + +pub(crate) trait DirectiveDefinitionIterExt { + fn with_location<'a>(self, location: DirectiveLocation) -> LocationFilter + where + Self: Iterator> + Sized; + + fn try_collect<'a>( + self, + u: &mut Unstructured, + doc: &Document, + schema: &Schema, + ) -> arbitrary::Result + where + Self: Iterator> + Sized; +} + +impl DirectiveDefinitionIterExt for I { + fn with_location<'a>(self, location: DirectiveLocation) -> LocationFilter + where + I: Iterator>, + Self: Sized, + { + LocationFilter(self, location) + } + + fn try_collect<'a>( + mut self, + u: &mut Unstructured, + doc: &Document, + schema: &Schema, + ) -> arbitrary::Result + where + Self: Iterator> + Sized, + { + let mut directives = DirectiveList::new(); + while let Some(d) = self.next() { + let mut arguments = Vec::new(); + for arg in &d.arguments { + if arg.is_required() || u.arbitrary()? { + arguments.push(Node::new(Argument { + name: arg.name.clone(), + value: Node::new(doc.arbitrary_value(u, arg.ty.deref(), schema)?), + })) + } + } + + directives.push(Node::new(Directive { + name: d.name.clone(), + arguments, + })) + } + Ok(directives) + } +} diff --git a/crates/apollo-smith/src/next/ast/document.rs b/crates/apollo-smith/src/next/ast/document.rs new file mode 100644 index 000000000..6c984413f --- /dev/null +++ b/crates/apollo-smith/src/next/ast/document.rs @@ -0,0 +1,489 @@ +use std::collections::HashSet; + +use arbitrary::Result; +use arbitrary::Unstructured; +use paste::paste; + +use apollo_compiler::ast::{ + Definition, DirectiveDefinition, DirectiveLocation, Document, EnumTypeDefinition, + EnumTypeExtension, EnumValueDefinition, FieldDefinition, FragmentDefinition, + InputObjectTypeDefinition, InputObjectTypeExtension, InputValueDefinition, + InterfaceTypeDefinition, InterfaceTypeExtension, Name, ObjectTypeDefinition, + ObjectTypeExtension, OperationDefinition, OperationType, ScalarTypeDefinition, + ScalarTypeExtension, SchemaDefinition, SchemaExtension, Type, UnionTypeDefinition, + UnionTypeExtension, Value, +}; +use apollo_compiler::executable::DirectiveList; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::{Node, NodeStr, Schema}; + +use crate::next::ast::definition::{DefinitionExt, DefinitionKind}; +use crate::next::common::Common; +use crate::next::schema::extended_type::{ExtendedTypeExt, ExtendedTypeKind}; +use crate::next::unstructured::{UnstructuredExt, UnstructuredOption}; + +use super::super::schema::schema::SchemaExt; +use super::directive_definition::DirectiveDefinitionIterExt; + +/// Macro to create accessors for definitions +macro_rules! access { + ($ty: ty) => { + paste! { + fn []( + &self, + u: &mut Unstructured, + ) -> arbitrary::Result<&Node<$ty>> { + let mut existing = self + .target() + .definitions + .iter() + .filter_map(|d| { + if let Definition::$ty(definition) = d { + Some(definition) + } else { + None + } + }) + .collect::>(); + let idx = u.choose_index(existing.len()).map_err(|e|{ + if let arbitrary::Error::EmptyChoose = e { + panic!("no existing definitions of type {}", stringify!($ty)) + } else { + e + } + })?; + Ok(existing.remove(idx)) + } + + fn []( + &mut self, + u: &mut Unstructured, + ) -> arbitrary::Result<&mut Node<$ty>> { + let mut existing = self + .target_mut() + .definitions + .iter_mut() + .filter_map(|d| { + if let Definition::$ty(definition) = d { + Some(definition) + } else { + None + } + }) + .collect::>(); + let idx = u.choose_index(existing.len()).map_err(|e|{ + if let arbitrary::Error::EmptyChoose = e { + panic!("no existing definitions of type {}", stringify!($ty)) + } else { + e + } + })?; + Ok(existing.remove(idx)) + } + + fn []( + &self, + u: &mut Unstructured, + ) -> arbitrary::Result>> { + let existing = self + .target() + .definitions + .iter() + .filter_map(|d| { + if let Definition::$ty(definition) = d { + Some(definition) + } else { + None + } + }) + .filter(|_| u.arbitrary().unwrap_or(false)) + .collect::>(); + + Ok(existing) + } + } + }; +} + +pub(crate) trait DocumentExt: Common { + access!(OperationDefinition); + access!(FragmentDefinition); + access!(DirectiveDefinition); + access!(SchemaDefinition); + access!(ScalarTypeDefinition); + access!(ObjectTypeDefinition); + access!(InterfaceTypeDefinition); + access!(UnionTypeDefinition); + access!(EnumTypeDefinition); + access!(InputObjectTypeDefinition); + access!(SchemaExtension); + access!(ScalarTypeExtension); + access!(ObjectTypeExtension); + access!(InterfaceTypeExtension); + access!(UnionTypeExtension); + access!(EnumTypeExtension); + access!(InputObjectTypeExtension); + + fn arbitrary_schema_definition( + &self, + u: &mut Unstructured, + schema: &Schema, + ) -> Result { + Ok(SchemaDefinition { + description: self.arbitrary_node_str(u)?.optional(u)?, + directives: schema + .sample_directives(u)? + .into_iter() + .with_location(DirectiveLocation::Schema) + .try_collect(u, self.target(), schema)?, + root_operations: vec![ + Some(Node::new(( + OperationType::Query, + self.random_object_type_definition(u)?.name.clone(), + ))), + Node::new(( + OperationType::Mutation, + self.random_object_type_definition(u)?.name.clone(), + )) + .optional(u)?, + Node::new(( + OperationType::Subscription, + self.random_object_type_definition(u)?.name.clone(), + )) + .optional(u)?, + ] + .into_iter() + .filter_map(|op| op) + .collect(), + }) + } + fn arbitrary_definition_name(&self, u: &mut Unstructured, schema: &Schema) -> Result { + let existing_names = self + .target() + .definitions + .iter() + .filter_map(|d| d.name()) + .collect::>(); + loop { + let name = self.arbitrary_name(u)?; + if !existing_names.contains(&name) { + return Ok(name); + } + } + } + + fn arbitrary_object_type_definition( + &self, + u: &mut Unstructured, + schema: &Schema, + ) -> Result { + Ok(ObjectTypeDefinition { + description: self.arbitrary_node_str(u)?.optional(u)?, + name: self.arbitrary_definition_name(u, schema)?, + implements_interfaces: schema + .sample_interface_types(u)? + .iter() + .map(|i| i.name.clone()) + .collect(), + directives: schema + .sample_directives(u)? + .into_iter() + .with_location(DirectiveLocation::Object) + .try_collect(u, self.target(), schema)?, + fields: u.arbitrary_vec(0, 5, |u| { + Ok(Node::new(self.arbitrary_field_definition( + u, + schema, + DirectiveLocation::FieldDefinition, + )?)) + })?, + }) + } + + fn arbitrary_directive_definition( + &self, + u: &mut Unstructured, + schema: &Schema, + ) -> Result { + Ok(DirectiveDefinition { + description: self.arbitrary_node_str(u)?.optional(u)?, + name: self.arbitrary_definition_name(u, schema)?, + arguments: u.arbitrary_vec(0, 5, |u| { + Ok(Node::new(self.arbitrary_input_value_definition(u, schema)?)) + })?, + repeatable: u.arbitrary()?, + locations: self.arbitrary_directive_locations(u)?, + }) + } + + fn arbitrary_input_object_type_definition( + &self, + u: &mut Unstructured, + schema: &Schema, + ) -> Result { + Ok(InputObjectTypeDefinition { + description: self.arbitrary_node_str(u)?.optional(u)?, + name: self.arbitrary_name(u)?, + directives: schema + .sample_directives(u)? + .into_iter() + .with_location(DirectiveLocation::InputObject) + .try_collect(u, self.target(), schema)?, + fields: u.arbitrary_vec(0, 5, |u| { + Ok(Node::new(self.arbitrary_input_value_definition(u, schema)?)) + })?, + }) + } + + fn arbitrary_input_value_definition( + &self, + u: &mut Unstructured, + schema: &Schema, + ) -> Result { + let ty = schema + .random_type( + u, + vec![ + ExtendedTypeKind::InputObjectTypeDefinition, + ExtendedTypeKind::Scalar, + ], + )? + .ty(u)?; + let default_value = self + .arbitrary_value(u, &ty, schema)? + .optional(u)? + .map(Node::new); + Ok(InputValueDefinition { + description: self.arbitrary_node_str(u)?.optional(u)?, + name: self.unique_name(), + ty: Node::new(ty), + default_value, + directives: schema + .sample_directives(u)? + .into_iter() + .with_location(DirectiveLocation::InputFieldDefinition) + .try_collect(u, self.target(), schema)?, + }) + } + + fn arbitrary_enum_type_definition( + &self, + u: &mut Unstructured, + schema: &Schema, + ) -> Result { + Ok(EnumTypeDefinition { + description: self.arbitrary_node_str(u)?.optional(u)?, + name: self.arbitrary_definition_name(u, schema)?, + directives: schema + .sample_directives(u)? + .into_iter() + .with_location(DirectiveLocation::Enum) + .try_collect(u, self.target(), schema)?, + values: u.arbitrary_vec(0, 5, |u| { + Ok(Node::new(EnumValueDefinition { + description: self.arbitrary_node_str(u)?.optional(u)?, + value: self.arbitrary_name(u)?, + directives: schema + .sample_directives(u)? + .into_iter() + .with_location(DirectiveLocation::EnumValue) + .try_collect(u, self.target(), schema)?, + })) + })?, + }) + } + + fn arbitrary_union_type_definition( + &self, + u: &mut Unstructured, + schema: &Schema, + ) -> Result { + Ok(UnionTypeDefinition { + description: self.arbitrary_node_str(u)?.optional(u)?, + name: self.arbitrary_definition_name(u, schema)?, + directives: schema + .sample_directives(u)? + .into_iter() + .with_location(DirectiveLocation::Union) + .try_collect(u, self.target(), schema)?, + members: self + .sample_object_type_definitions(u)? + .iter() + .map(|i| i.name.clone()) + .collect(), + }) + } + + fn arbitrary_interface_type_definition( + &self, + u: &mut Unstructured, + schema: &Schema, + ) -> Result { + Ok(InterfaceTypeDefinition { + description: self.arbitrary_node_str(u)?.optional(u)?, + name: self.arbitrary_definition_name(u, schema)?, + implements_interfaces: self + .sample_interface_type_definitions(u)? + .iter() + .map(|interface| interface.name.clone()) + .collect(), + directives: schema + .sample_directives(u)? + .into_iter() + .with_location(DirectiveLocation::Interface) + .try_collect(u, self.target(), schema)?, + fields: u.arbitrary_vec(0, 5, |u| { + Ok(Node::new(self.arbitrary_field_definition( + u, + schema, + DirectiveLocation::InputFieldDefinition, + )?)) + })?, + }) + } + + fn arbitrary_field_definition( + &self, + u: &mut Unstructured, + schema: &Schema, + directive_location: DirectiveLocation, + ) -> Result { + Ok(FieldDefinition { + description: self.arbitrary_node_str(u)?.optional(u)?, + name: self.unique_name(), + arguments: u.arbitrary_vec(0, 5, |u| { + Ok(Node::new(self.arbitrary_input_value_definition(u, schema)?)) + })?, + ty: schema + .random_type( + u, + vec![ + ExtendedTypeKind::Scalar, + ExtendedTypeKind::Object, + ExtendedTypeKind::Enum, + ExtendedTypeKind::Union, + ExtendedTypeKind::Interface, + ], + )? + .ty(u)?, + directives: schema + .sample_directives(u)? + .into_iter() + .with_location(directive_location) + .try_collect(u, self.target(), schema)?, + }) + } + + fn arbitrary_value(&self, u: &mut Unstructured, ty: &Type, schema: &Schema) -> Result { + match ty { + Type::Named(ty) => { + if u.arbitrary()? { + self.arbitrary_value(u, &Type::NonNullNamed(ty.clone()), schema) + } else { + Ok(Value::Null) + } + } + Type::List(ty) => { + if u.arbitrary()? { + Ok(Value::List(u.arbitrary_vec(0, 5, |u| { + Ok(Node::new(self.arbitrary_value(u, ty, schema)?)) + })?)) + } else { + Ok(Value::Null) + } + } + Type::NonNullNamed(ty) => match schema.types.get(ty).expect("type must exist") { + ExtendedType::Scalar(ty) => { + if ty.name == "Int" { + Ok(Value::from(u.arbitrary::()?)) + } else if ty.name == "Float" { + Ok(Value::from(u.arbitrary::()?)) + } else if ty.name == "Boolean" { + Ok(Value::Boolean(u.arbitrary()?)) + } else { + Ok(Value::String(self.arbitrary_node_str(u)?)) + } + } + ExtendedType::Object(ty) => { + let mut values = Vec::new(); + for (name, definition) in &ty.fields { + values.push(( + name.clone(), + Node::new(self.arbitrary_value(u, &definition.ty, schema)?), + )); + } + Ok(Value::Object(values)) + } + ExtendedType::Enum(ty) => { + let values = ty + .values + .iter() + .map(|(name, v)| &v.value) + .collect::>(); + if values.is_empty() { + panic!("enum must have at least one value") + } else { + Ok(Value::Enum((*u.choose(&values)?).clone())) + } + } + ExtendedType::InputObject(ty) => { + let mut values = Vec::new(); + for (name, definition) in &ty.fields { + values.push(( + name.clone(), + Node::new(self.arbitrary_value(u, &definition.ty, schema)?), + )); + } + Ok(Value::Object(values)) + } + _ => { + panic!("type must be a scalar, object, enum or input object") + } + }, + Type::NonNullList(ty) => Ok(Value::List(u.arbitrary_vec(0, 5, |u| { + Ok(Node::new(self.arbitrary_value(u, ty, schema)?)) + })?)), + } + } + + fn random_definition( + &self, + u: &mut Unstructured, + types: Vec, + ) -> Result<&Definition> { + let definitions = self + .target() + .definitions + .iter() + .filter(|d| types.iter().any(|t| t.matches(*d))) + .collect::>(); + Ok(u.choose(definitions.as_slice()).map_err(|e| { + if let arbitrary::Error::EmptyChoose = e { + panic!("no existing definitions of types {:?}", types) + } else { + e + } + })?) + } + + fn definition(&self, name: &Name) -> Option<&Definition> { + self.target() + .definitions + .iter() + .find(|d| d.name() == Some(&name)) + } + + fn target(&self) -> &Document; + fn target_mut(&mut self) -> &mut Document; +} + +impl DocumentExt for Document { + fn target(&self) -> &Document { + self + } + fn target_mut(&mut self) -> &mut Document { + self + } +} + +impl Common for Document {} diff --git a/crates/apollo-smith/src/next/ast/mod.rs b/crates/apollo-smith/src/next/ast/mod.rs new file mode 100644 index 000000000..39d40f880 --- /dev/null +++ b/crates/apollo-smith/src/next/ast/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod definition; +pub(crate) mod directive_definition; +pub(crate) mod document; +pub(crate) mod object_type_definition; diff --git a/crates/apollo-smith/src/next/ast/object_type_definition.rs b/crates/apollo-smith/src/next/ast/object_type_definition.rs new file mode 100644 index 000000000..7a13ad883 --- /dev/null +++ b/crates/apollo-smith/src/next/ast/object_type_definition.rs @@ -0,0 +1,24 @@ +use arbitrary::Unstructured; + +use apollo_compiler::ast::ObjectTypeDefinition; +use apollo_compiler::Node; + +use crate::next::common::Common; + +pub(crate) trait ObjectTypeDefinitionExt: Common { + field_access!(); + + fn target(&self) -> &ObjectTypeDefinition; + fn target_mut(&mut self) -> &mut ObjectTypeDefinition; +} + +impl ObjectTypeDefinitionExt for ObjectTypeDefinition { + fn target(&self) -> &ObjectTypeDefinition { + self + } + fn target_mut(&mut self) -> &mut ObjectTypeDefinition { + self + } +} + +impl Common for ObjectTypeDefinition {} diff --git a/crates/apollo-smith/src/next/common.rs b/crates/apollo-smith/src/next/common.rs new file mode 100644 index 000000000..5142c04e0 --- /dev/null +++ b/crates/apollo-smith/src/next/common.rs @@ -0,0 +1,75 @@ +//static Atomic integer +use std::sync::atomic::{AtomicUsize, Ordering}; + +use arbitrary::Unstructured; + +use apollo_compiler::ast::{DirectiveLocation, Name}; +use apollo_compiler::NodeStr; + +static COUNTER: AtomicUsize = AtomicUsize::new(0); + +pub(crate) trait Common { + fn unique_name(&self) -> Name { + Name::new(NodeStr::new(&format!( + "f{}", + COUNTER.fetch_add(1, Ordering::SeqCst) + ))) + .expect("valid name") + } + fn arbitrary_name(&self, u: &mut Unstructured) -> arbitrary::Result { + loop { + let s: String = u.arbitrary()?; + let idx = s + .char_indices() + .nth(10) + .map(|(s, _c)| s) + .unwrap_or_else(|| s.len()); + if let Ok(name) = Name::new(s[..idx].to_string()) { + return Ok(name); + } + } + } + + fn arbitrary_node_str(&self, u: &mut Unstructured) -> arbitrary::Result { + let s: String = u.arbitrary()?; + let idx = s + .char_indices() + .nth(10) + .map(|(s, _c)| s) + .unwrap_or_else(|| s.len()); + Ok(NodeStr::new(&s[..idx])) + } + + fn arbitrary_directive_locations( + &self, + u: &mut Unstructured, + ) -> arbitrary::Result> { + let mut locations = Vec::new(); + for _ in 0..u.int_in_range(1..=5)? { + locations.push( + u.choose(&[ + DirectiveLocation::Query, + DirectiveLocation::Mutation, + DirectiveLocation::Subscription, + DirectiveLocation::Field, + DirectiveLocation::FragmentDefinition, + DirectiveLocation::FragmentSpread, + DirectiveLocation::InlineFragment, + DirectiveLocation::Schema, + DirectiveLocation::Scalar, + DirectiveLocation::Object, + DirectiveLocation::FieldDefinition, + DirectiveLocation::ArgumentDefinition, + DirectiveLocation::Interface, + DirectiveLocation::Union, + DirectiveLocation::Enum, + DirectiveLocation::EnumValue, + DirectiveLocation::InputObject, + DirectiveLocation::InputFieldDefinition, + ])? + .clone(), + ); + } + Ok(locations) + } +} diff --git a/crates/apollo-smith/src/next/document.rs b/crates/apollo-smith/src/next/document.rs deleted file mode 100644 index 37df558b2..000000000 --- a/crates/apollo-smith/src/next/document.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::next::unstructured::Unstructured; -use apollo_compiler::ast::{FieldDefinition, ObjectTypeDefinition}; -use std::ops::{Deref, DerefMut}; - -pub(crate) struct Document<'u, 'ue, 'd, 'ad> { - pub(crate) u: &'d mut Unstructured<'u, 'ue>, - pub(crate) doc: &'ad mut apollo_compiler::ast::Document, -} -impl<'u, 'ue, 'e, 'ad> Document<'u, 'ue, 'e, 'ad> { - pub(crate) fn with_object_type_definition( - &mut self, - callback: fn(&mut Unstructured, ty: &mut ObjectTypeDefinition) -> arbitrary::Result<()>, - ) -> arbitrary::Result<()> { - let mut definitions = self - .doc - .definitions - .iter_mut() - .filter_map(|def| match def { - apollo_compiler::ast::Definition::ObjectTypeDefinition(def) => Some(def.make_mut()), - _ => None, - }) - .collect::>(); - - let idx = self.u.choose_index(definitions.len())?; - - Ok(callback(self.u, definitions[idx])?) - } - - pub(crate) fn with_field_definition( - &mut self, - callback: fn(&mut Unstructured, doc: &mut FieldDefinition) -> arbitrary::Result<()>, - ) -> arbitrary::Result<()> { - let mut definitions = self - .doc - .definitions - .iter_mut() - .filter_map(|def| match def { - apollo_compiler::ast::Definition::ObjectTypeDefinition(def) => Some(def.make_mut()), - _ => None, - }) - .collect::>(); - - let idx = self.u.choose_index(definitions.len())?; - - Ok(callback(self.u, definitions[idx])?) - } -} - -impl<'u, 'ue, 'd, 'ad> Deref for Document<'u, 'ue, 'd, 'ad> { - type Target = Unstructured<'u, 'ue>; - - fn deref(&self) -> &Self::Target { - &self.u - } -} - -impl<'u, 'ue, 'd, 'ad> DerefMut for Document<'u, 'ue, 'd, 'ad> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.u - } -} diff --git a/crates/apollo-smith/src/next/existing.rs b/crates/apollo-smith/src/next/existing.rs deleted file mode 100644 index bcc4e7cc3..000000000 --- a/crates/apollo-smith/src/next/existing.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::next::unstructured::Unstructured; -use apollo_compiler::ast::{Name, Type}; -use std::ops::{Deref, DerefMut}; - -pub(crate) struct Existing<'u, 'ue, 'e>(pub(crate) &'e mut Unstructured<'u, 'ue>); -impl<'u, 'ue, 'e> Existing<'u, 'ue, 'e> { - pub(crate) fn type_name(&mut self) -> arbitrary::Result { - let names = self.schema().types.keys().cloned().collect::>(); - assert!(!names.is_empty()); - Ok(self.choose(&names)?.clone()) - } - - pub(crate) fn ty(&mut self) -> arbitrary::Result { - let name = self.type_name()?; - self.wrap_ty(name) - } -} - -impl<'u, 'ue, 'n> Deref for Existing<'u, 'ue, 'n> { - type Target = Unstructured<'u, 'ue>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'u, 'ue, 'n> DerefMut for Existing<'u, 'ue, 'n> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} diff --git a/crates/apollo-smith/src/next/invalid.rs b/crates/apollo-smith/src/next/invalid.rs deleted file mode 100644 index 2b3a3f375..000000000 --- a/crates/apollo-smith/src/next/invalid.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::next::unstructured::Unstructured; -use std::ops::{Deref, DerefMut}; - -pub(crate) struct Invalid<'u, 'ue, 'e>(pub(crate) &'e mut Unstructured<'u, 'ue>); -impl<'u, 'ue, 'e> Invalid<'u, 'ue, 'e> {} - -impl<'u, 'ue, 'n> Deref for Invalid<'u, 'ue, 'n> { - type Target = Unstructured<'u, 'ue>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'u, 'ue, 'n> DerefMut for Invalid<'u, 'ue, 'n> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} diff --git a/crates/apollo-smith/src/next/mod.rs b/crates/apollo-smith/src/next/mod.rs index 5644fe454..2a169c4f1 100644 --- a/crates/apollo-smith/src/next/mod.rs +++ b/crates/apollo-smith/src/next/mod.rs @@ -1,59 +1,143 @@ -use arbitrary::Result; +use std::any::type_name; +use std::path::PathBuf; -use crate::next::mutations::all_mutations; -use crate::next::unstructured::Unstructured; +use arbitrary::Unstructured; -mod document; -mod existing; -mod invalid; -mod mutations; -mod unstructured; -mod valid; - -pub fn build_document(u: &mut arbitrary::Unstructured) -> Result<()> { - let mut doc = apollo_compiler::ast::Document::new(); - - let mut mutations = all_mutations(); - mutations.retain(|_| { - u.arbitrary() - .expect("fuzzer must be able to generate a bool") - }); - if mutations.is_empty() { - return Ok(()); - } +use apollo_compiler::ast::Document; +use apollo_compiler::validation::WithErrors; +use apollo_compiler::Schema; - let mut schema = apollo_compiler::Schema::builder() - .add_ast(&doc) - .build() - .expect("initial document must be valid"); - for _ in 0..1000 { - let u = &mut Unstructured::new(u, &schema); - let mutation = u.choose(&mut mutations)?; - if mutation.apply(u, &mut doc).is_ok() { - match apollo_compiler::Schema::builder().add_ast(&doc).build() { - Ok(new_schema) if mutation.is_valid() => schema = new_schema, - Ok(_new_schema) => { - panic!("valid schema returned from invalid mutation") - } - Err(_new_schema) if mutation.is_valid() => { - panic!("invalid schema returned from valid mutation") +/// macro for accessing fields +macro_rules! field_access { + () => { + fn random_field( + &self, + u: &mut Unstructured, + ) -> arbitrary::Result<&Node> { + Ok(u.choose(&self.target().fields).map_err(|e| { + if let arbitrary::Error::EmptyChoose = e { + panic!("no existing fields") + } else { + e } - Err(_new_schema) => { - break; + })?) + } + + fn random_field_mut( + &mut self, + u: &mut Unstructured, + ) -> arbitrary::Result<&mut Node> { + let idx = u.choose_index(self.target().fields.len()).map_err(|e| { + if let arbitrary::Error::EmptyChoose = e { + panic!("no existing fields") + } else { + e } + })?; + Ok(&mut self.target_mut().fields[idx]) + } + + fn sample_fields( + &self, + u: &mut Unstructured, + ) -> arbitrary::Result>> { + let existing = self + .target() + .fields + .iter() + .filter(|_| u.arbitrary().unwrap_or(false)) + .collect::>(); + + Ok(existing) + } + }; +} +mod ast; +mod common; +mod mutations; +mod schema; +mod unstructured; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("arbitrary error")] + Arbitrary(#[from] arbitrary::Error), + + #[error("schema validation")] + Validation(WithErrors), + + #[error("schema validation passed, but should have failed")] + ExpectedValidationFail(Document), + + #[error("parse error")] + Parse(WithErrors), +} + +pub fn generate_schema_document(input: &[u8]) -> Result { + let mut u = Unstructured::new(input); + println!("starting"); + let mut doc = Document::parse( + "type Query { me: String }".to_string(), + PathBuf::from("synthetic"), + ) + .map_err(Error::Parse)?; // Start with a minimal schema + println!("parsed initial"); + let mutations = mutations::all_mutations(); + let mut schema = doc.to_schema().expect("initial schema must be valid"); + for n in 0..1000 { + println!("iteration: {}", n); + let mut mutation = u.choose(&mutations)?; + println!("applying mutation: {} ", mutation.type_name()); + // First let's modify the document. We use the schema because it has all the built-in definitions in it. + mutation.apply(&mut u, &mut doc, &schema)?; + + // Let's reparse the document to check that it can be parsed + Document::parse(doc.to_string(), PathBuf::from("synthetic")).map_err(Error::Parse)?; + // Now let's validate that the schema says it's OK + + println!("{}", doc.to_string()); + + match (mutation.is_valid(), doc.to_schema_validate()) { + (true, Ok(new_schema)) => { + schema = new_schema.into_inner(); + continue; + } + (true, Err(e)) => { + return Err(Error::Validation(e)); + } + (false, Ok(_)) => { + return Err(Error::ExpectedValidationFail(doc)); + } + (false, Err(_)) => { + // Validation was expected to fail, we can't continue + return Ok(doc); } } } - Ok(()) + + Ok(doc) } #[cfg(test)] mod test { + use crate::next::{generate_schema_document, Error}; + use apollo_compiler::ast::Document; + use apollo_compiler::Schema; + + #[test] + fn test_schema() { + let f = Schema::builder().add_ast(&Document::new()).build().unwrap(); + println!("{:?}", f.types.len()); + } + #[test] fn test() { - let mut u = arbitrary::Unstructured::new(&[0; 32]); - if let Err(e) = super::build_document(&mut u) { - panic!("error: {:?}", e); - }; + let input = b"293ur928jff029jf0293f"; + match generate_schema_document(input) { + Ok(_) => {} + Err(e) => { + panic!("error: {:?}", e) + } + } } } diff --git a/crates/apollo-smith/src/next/mutations/add_directive_definition.rs b/crates/apollo-smith/src/next/mutations/add_directive_definition.rs new file mode 100644 index 000000000..e1b2317b1 --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/add_directive_definition.rs @@ -0,0 +1,27 @@ +use arbitrary::Unstructured; + +use apollo_compiler::ast::{Definition, Document}; +use apollo_compiler::{Node, Schema}; + +use crate::next::ast::document::DocumentExt; +use crate::next::mutations::Mutation; + +pub(crate) struct AddDirectiveDefinition; +impl Mutation for AddDirectiveDefinition { + fn apply( + &self, + u: &mut Unstructured, + doc: &mut Document, + schema: &Schema, + ) -> arbitrary::Result<()> { + doc.definitions + .push(Definition::DirectiveDefinition(Node::new( + doc.arbitrary_directive_definition(u, schema)?, + ))); + + Ok(()) + } + fn is_valid(&self) -> bool { + true + } +} diff --git a/crates/apollo-smith/src/next/mutations/add_directive_to_object_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_directive_to_object_type_definition.rs deleted file mode 100644 index 75bbbcfc2..000000000 --- a/crates/apollo-smith/src/next/mutations/add_directive_to_object_type_definition.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::next::mutations::Mutation; -use crate::next::unstructured::Unstructured; -use apollo_compiler::ast::Document; -use apollo_compiler::Node; - -pub(crate) struct AddField; -impl Mutation for AddField { - fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result<()> { - u.document(doc).with_object_type_definition(|u, o| { - let mut field_definition = u.valid().field_definition()?; - let existing_field_names = o.fields.iter().map(|f| &f.name).collect::>(); - field_definition.name = u.arbitrary_unique_name(&existing_field_names)?; - o.fields.push(Node::new(u.valid().field_definition()?)); - Ok(()) - })?; - Ok(()) - } - fn is_valid(&self) -> bool { - true - } -} diff --git a/crates/apollo-smith/src/next/mutations/add_enum_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_enum_type_definition.rs new file mode 100644 index 000000000..2d545f110 --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/add_enum_type_definition.rs @@ -0,0 +1,27 @@ +use arbitrary::Unstructured; + +use apollo_compiler::ast::{Definition, Document}; +use apollo_compiler::{Node, Schema}; + +use crate::next::ast::document::DocumentExt; +use crate::next::mutations::Mutation; + +pub(crate) struct AddEnumTypeDefinition; +impl Mutation for AddEnumTypeDefinition { + fn apply( + &self, + u: &mut Unstructured, + doc: &mut Document, + schema: &Schema, + ) -> arbitrary::Result<()> { + doc.definitions + .push(Definition::EnumTypeDefinition(Node::new( + doc.arbitrary_enum_type_definition(u, schema)?, + ))); + + Ok(()) + } + fn is_valid(&self) -> bool { + true + } +} diff --git a/crates/apollo-smith/src/next/mutations/add_field.rs b/crates/apollo-smith/src/next/mutations/add_field.rs deleted file mode 100644 index 75bbbcfc2..000000000 --- a/crates/apollo-smith/src/next/mutations/add_field.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::next::mutations::Mutation; -use crate::next::unstructured::Unstructured; -use apollo_compiler::ast::Document; -use apollo_compiler::Node; - -pub(crate) struct AddField; -impl Mutation for AddField { - fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result<()> { - u.document(doc).with_object_type_definition(|u, o| { - let mut field_definition = u.valid().field_definition()?; - let existing_field_names = o.fields.iter().map(|f| &f.name).collect::>(); - field_definition.name = u.arbitrary_unique_name(&existing_field_names)?; - o.fields.push(Node::new(u.valid().field_definition()?)); - Ok(()) - })?; - Ok(()) - } - fn is_valid(&self) -> bool { - true - } -} diff --git a/crates/apollo-smith/src/next/mutations/add_input_object_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_input_object_type_definition.rs new file mode 100644 index 000000000..03d396797 --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/add_input_object_type_definition.rs @@ -0,0 +1,27 @@ +use arbitrary::Unstructured; + +use apollo_compiler::ast::{Definition, Document}; +use apollo_compiler::{Node, Schema}; + +use crate::next::ast::document::DocumentExt; +use crate::next::mutations::Mutation; + +pub(crate) struct AddInputObjectTypeDefinition; +impl Mutation for AddInputObjectTypeDefinition { + fn apply( + &self, + u: &mut Unstructured, + doc: &mut Document, + schema: &Schema, + ) -> arbitrary::Result<()> { + doc.definitions + .push(Definition::InputObjectTypeDefinition(Node::new( + doc.arbitrary_input_object_type_definition(u, schema)?, + ))); + Ok(()) + } + + fn is_valid(&self) -> bool { + true + } +} diff --git a/crates/apollo-smith/src/next/mutations/add_interface_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_interface_type_definition.rs new file mode 100644 index 000000000..a2fb135c1 --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/add_interface_type_definition.rs @@ -0,0 +1,27 @@ +use arbitrary::Unstructured; + +use apollo_compiler::ast::{Definition, Document}; +use apollo_compiler::{Node, Schema}; + +use crate::next::ast::document::DocumentExt; +use crate::next::mutations::Mutation; + +pub(crate) struct AddInterfaceTypeDefinition; +impl Mutation for AddInterfaceTypeDefinition { + fn apply( + &self, + u: &mut Unstructured, + doc: &mut Document, + schema: &Schema, + ) -> arbitrary::Result<()> { + doc.definitions + .push(Definition::InterfaceTypeDefinition(Node::new( + doc.arbitrary_interface_type_definition(u, schema)?, + ))); + + Ok(()) + } + fn is_valid(&self) -> bool { + true + } +} diff --git a/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs index e153c2e28..2eb84ab67 100644 --- a/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs @@ -1,16 +1,26 @@ +use arbitrary::Unstructured; + +use apollo_compiler::ast::{Definition, Document}; +use apollo_compiler::{Node, Schema}; + +use crate::next::ast::document::DocumentExt; use crate::next::mutations::Mutation; -use crate::next::unstructured::Unstructured; -use apollo_compiler::ast::Document; -pub(crate) struct AddObjectType; -impl Mutation for AddObjectType { - fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result<()> { +pub(crate) struct AddObjectTypeDefinition; +impl Mutation for AddObjectTypeDefinition { + fn apply( + &self, + u: &mut Unstructured, + doc: &mut Document, + schema: &Schema, + ) -> arbitrary::Result<()> { doc.definitions - .push(apollo_compiler::ast::Definition::ObjectTypeDefinition( - u.valid().object_type_definition()?.into(), - )); + .push(Definition::ObjectTypeDefinition(Node::new( + doc.arbitrary_object_type_definition(u, schema)?, + ))); Ok(()) } + fn is_valid(&self) -> bool { true } diff --git a/crates/apollo-smith/src/next/mutations/add_schema_definition.rs b/crates/apollo-smith/src/next/mutations/add_schema_definition.rs new file mode 100644 index 000000000..00035c613 --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/add_schema_definition.rs @@ -0,0 +1,27 @@ +use arbitrary::Unstructured; + +use apollo_compiler::ast::{Definition, Document}; +use apollo_compiler::{Node, Schema}; + +use crate::next::ast::document::DocumentExt; +use crate::next::mutations::Mutation; + +pub(crate) struct AddSchemaDefiniton; +impl Mutation for AddSchemaDefiniton { + fn apply( + &self, + u: &mut Unstructured, + doc: &mut Document, + schema: &Schema, + ) -> arbitrary::Result<()> { + // If the document already has a schema definition, we don't need to add another one + doc.definitions.push(Definition::SchemaDefinition(Node::new( + doc.arbitrary_schema_definition(u, schema)?, + ))); + + Ok(()) + } + fn is_valid(&self) -> bool { + true + } +} diff --git a/crates/apollo-smith/src/next/mutations/add_self_field.rs b/crates/apollo-smith/src/next/mutations/add_self_field.rs deleted file mode 100644 index faf7ce99d..000000000 --- a/crates/apollo-smith/src/next/mutations/add_self_field.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::next::mutations::Mutation; -use crate::next::unstructured::Unstructured; -use apollo_compiler::ast::Document; -use apollo_compiler::Node; - -pub(crate) struct AddField; -impl Mutation for AddField { - fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result<()> { - u.document(doc).with_object_type_definition(|u, o| { - let type_name = o.name.clone(); - let mut field_definition = u.valid().field_definition()?; - let existing_field_names = o.fields.iter().map(|f| &f.name).collect::>(); - field_definition.name = u.arbitrary_unique_name(&existing_field_names)?; - field_definition.ty = u.wrap_ty(type_name)?; - o.fields.push(Node::new(u.valid().field_definition()?)); - Ok(()) - })?; - Ok(()) - } - fn is_valid(&self) -> bool { - true - } -} diff --git a/crates/apollo-smith/src/next/mutations/add_union_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_union_type_definition.rs new file mode 100644 index 000000000..5c33958c7 --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/add_union_type_definition.rs @@ -0,0 +1,27 @@ +use arbitrary::Unstructured; + +use apollo_compiler::ast::{Definition, Document}; +use apollo_compiler::{Node, Schema}; + +use crate::next::ast::document::DocumentExt; +use crate::next::mutations::Mutation; + +pub(crate) struct AddUnionTypeDefinition; +impl Mutation for AddUnionTypeDefinition { + fn apply( + &self, + u: &mut Unstructured, + doc: &mut Document, + schema: &Schema, + ) -> arbitrary::Result<()> { + doc.definitions + .push(Definition::UnionTypeDefinition(Node::new( + doc.arbitrary_union_type_definition(u, schema)?, + ))); + + Ok(()) + } + fn is_valid(&self) -> bool { + true + } +} diff --git a/crates/apollo-smith/src/next/mutations/mod.rs b/crates/apollo-smith/src/next/mutations/mod.rs index 57854169f..7e7aaf15f 100644 --- a/crates/apollo-smith/src/next/mutations/mod.rs +++ b/crates/apollo-smith/src/next/mutations/mod.rs @@ -1,18 +1,46 @@ -mod add_directive_to_object_type_definition; -mod add_field; -mod add_object_type_definition; -mod add_self_field; -mod remove_all_fields; +use arbitrary::Unstructured; +use std::any::type_name; -use crate::next::mutations::add_object_type_definition::AddObjectType; -use crate::next::unstructured::Unstructured; +use crate::next::mutations::add_directive_definition::AddDirectiveDefinition; +use crate::next::mutations::add_input_object_type_definition::AddInputObjectTypeDefinition; +use crate::next::mutations::add_interface_type_definition::AddInterfaceTypeDefinition; use apollo_compiler::ast::Document; +use apollo_compiler::Schema; + +use crate::next::mutations::add_object_type_definition::AddObjectTypeDefinition; +use crate::next::mutations::add_schema_definition::AddSchemaDefiniton; +use crate::next::mutations::add_union_type_definition::AddUnionTypeDefinition; + +mod add_directive_definition; +mod add_enum_type_definition; +mod add_input_object_type_definition; +mod add_interface_type_definition; +mod add_object_type_definition; +mod add_schema_definition; +mod add_union_type_definition; +mod remove_all_fields; pub(crate) trait Mutation { - fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result<()>; + fn apply( + &self, + u: &mut Unstructured, + doc: &mut Document, + schema: &Schema, + ) -> arbitrary::Result<()>; fn is_valid(&self) -> bool; + + fn type_name(&self) -> &str { + type_name::() + } } pub(crate) fn all_mutations() -> Vec> { - vec![Box::new(AddObjectType)] + vec![ + Box::new(AddObjectTypeDefinition), + Box::new(AddInterfaceTypeDefinition), + Box::new(AddDirectiveDefinition), + Box::new(AddInputObjectTypeDefinition), + Box::new(AddUnionTypeDefinition), + Box::new(AddInterfaceTypeDefinition), + ] } diff --git a/crates/apollo-smith/src/next/mutations/remove_all_fields.rs b/crates/apollo-smith/src/next/mutations/remove_all_fields.rs index eedc6f5e5..dff3a4c20 100644 --- a/crates/apollo-smith/src/next/mutations/remove_all_fields.rs +++ b/crates/apollo-smith/src/next/mutations/remove_all_fields.rs @@ -1,12 +1,22 @@ use crate::next::mutations::Mutation; -use crate::next::unstructured::Unstructured; +use arbitrary::Unstructured; + +use crate::next::ast::document::DocumentExt; use apollo_compiler::ast::Document; +use apollo_compiler::Schema; pub(crate) struct RemoveAllFields; impl Mutation for RemoveAllFields { - fn apply(&self, u: &mut Unstructured, doc: &mut Document) -> arbitrary::Result<()> { - u.document(doc) - .with_object_type_definition(|_u, o| Ok(o.fields.clear()))?; + fn apply( + &self, + u: &mut Unstructured, + doc: &mut Document, + schema: &Schema, + ) -> arbitrary::Result<()> { + doc.random_object_type_definition_mut(u)? + .make_mut() + .fields + .clear(); Ok(()) } fn is_valid(&self) -> bool { diff --git a/crates/apollo-smith/src/next/schema/extended_type.rs b/crates/apollo-smith/src/next/schema/extended_type.rs new file mode 100644 index 000000000..1b371104b --- /dev/null +++ b/crates/apollo-smith/src/next/schema/extended_type.rs @@ -0,0 +1,54 @@ +use apollo_compiler::ast::{Definition, Name, Type}; +use apollo_compiler::schema::ExtendedType; +use arbitrary::Unstructured; + +pub(crate) trait ExtendedTypeExt { + fn ty(&self, u: &mut Unstructured) -> arbitrary::Result; +} + +impl ExtendedTypeExt for ExtendedType { + fn ty(&self, u: &mut Unstructured) -> arbitrary::Result { + Ok(ty(u, self.name().clone())?) + } +} + +fn ty(u: &mut Unstructured, name: Name) -> arbitrary::Result { + let mut ty = if u.arbitrary()? { + Type::Named(name) + } else { + Type::NonNullNamed(name) + }; + + for _ in 0..u.int_in_range(0..=5)? { + if u.arbitrary()? { + ty = Type::List(Box::new(ty)) + } else { + ty = Type::NonNullList(Box::new(ty)) + }; + } + Ok(ty) +} + +#[derive(Debug)] +pub(crate) enum ExtendedTypeKind { + Scalar, + Object, + Interface, + Union, + Enum, + InputObjectTypeDefinition, +} + +impl ExtendedTypeKind { + pub(crate) fn matches(&self, definition: &ExtendedType) -> bool { + match (self, definition) { + (Self::Scalar, ExtendedType::Scalar(_)) => true, + (Self::Object, ExtendedType::Object(_)) => true, + (Self::Interface, ExtendedType::Interface(_)) => true, + (Self::Union, ExtendedType::Union(_)) => true, + (Self::Enum, ExtendedType::Enum(_)) => true, + (Self::InputObjectTypeDefinition, ExtendedType::InputObject(_)) => true, + _ => false, + } + } +} diff --git a/crates/apollo-smith/src/next/schema/mod.rs b/crates/apollo-smith/src/next/schema/mod.rs new file mode 100644 index 000000000..9032d864c --- /dev/null +++ b/crates/apollo-smith/src/next/schema/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod extended_type; +pub(crate) mod schema; diff --git a/crates/apollo-smith/src/next/schema/schema.rs b/crates/apollo-smith/src/next/schema/schema.rs new file mode 100644 index 000000000..48b2bcf5a --- /dev/null +++ b/crates/apollo-smith/src/next/schema/schema.rs @@ -0,0 +1,131 @@ +use crate::next::ast::definition::DefinitionKind; +use crate::next::schema::extended_type::ExtendedTypeKind; +use apollo_compiler::ast::Definition; +use apollo_compiler::schema::{DirectiveDefinition, ExtendedType}; +use apollo_compiler::schema::{ + EnumType, InputObjectType, InterfaceType, ObjectType, ScalarType, UnionType, +}; +use apollo_compiler::Node; +use apollo_compiler::Schema; +use arbitrary::Unstructured; +use paste::paste; +macro_rules! access { + ($variant: ident, $ty: ty) => { + paste! { + fn []( + &self, + u: &mut Unstructured, + ) -> arbitrary::Result<&Node<$ty>> { + let mut existing = self + .target() + .types.values() + .filter_map(|d| { + if let ExtendedType::$variant(definition) = d { + Some(definition) + } else { + None + } + }) + .collect::>(); + let idx = u.choose_index(existing.len()).map_err(|e|{ + if let arbitrary::Error::EmptyChoose = e { + panic!("no existing definitions of type {}", stringify!($ty)) + } else { + e + } + })?; + Ok(existing.remove(idx)) + } + + fn []( + &self, + u: &mut Unstructured, + ) -> arbitrary::Result>> { + let existing = self + .target() + .types.values() + .filter_map(|d| { + if let ExtendedType::$variant(definition) = d { + Some(definition) + } else { + None + } + }) + .filter(|_| u.arbitrary().unwrap_or(false)) + .collect::>(); + + Ok(existing) + } + } + }; +} + +pub(crate) trait SchemaExt { + access!(Scalar, ScalarType); + access!(Object, ObjectType); + access!(Interface, InterfaceType); + access!(Union, UnionType); + access!(Enum, EnumType); + access!(InputObject, InputObjectType); + + fn random_type( + &self, + u: &mut Unstructured, + types: Vec, + ) -> arbitrary::Result<&ExtendedType> { + let definitions = self + .target() + .types + .values() + .filter(|d| types.iter().any(|t| t.matches(*d))) + .collect::>(); + Ok(u.choose(definitions.as_slice()).map_err(|e| { + if let arbitrary::Error::EmptyChoose = e { + panic!("no existing definitions of types {:?}", types) + } else { + e + } + })?) + } + + fn random_directive( + &self, + u: &mut Unstructured, + ) -> arbitrary::Result<&Node> { + let mut existing = self + .target() + .directive_definitions + .values() + .collect::>(); + let idx = u.choose_index(existing.len()).map_err(|e| { + if let arbitrary::Error::EmptyChoose = e { + panic!("no existing directive definitions") + } else { + e + } + })?; + Ok(existing.remove(idx)) + } + + fn sample_directives( + &self, + u: &mut Unstructured, + ) -> arbitrary::Result>> { + let existing = self + .target() + .directive_definitions + .values() + .filter(|_| u.arbitrary().unwrap_or(false)) + .collect::>(); + + Ok(existing) + } + + fn target(&self) -> &Schema; +} + +impl SchemaExt for Schema { + fn target(&self) -> &Schema { + &self + } +} diff --git a/crates/apollo-smith/src/next/test.graphql b/crates/apollo-smith/src/next/test.graphql new file mode 100644 index 000000000..10640c312 --- /dev/null +++ b/crates/apollo-smith/src/next/test.graphql @@ -0,0 +1,7 @@ + +type Query { +} + +type Foo { + +} diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index 6265c9d33..814daa097 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -1,99 +1,43 @@ -use crate::next::document::Document; -use crate::next::existing::Existing; -use crate::next::invalid::Invalid; -use crate::next::valid::Valid; -use apollo_compiler::ast::{Name, Type}; -use apollo_compiler::NodeStr; use arbitrary::Result; -use std::ops::{Deref, DerefMut}; -pub(crate) struct Unstructured<'u, 'ue> { - pub(crate) u: &'ue mut arbitrary::Unstructured<'u>, - pub(crate) schema: &'ue apollo_compiler::Schema, -} - -impl Unstructured<'_, '_> { - pub(crate) fn new<'u, 'ue>( - u: &'ue mut arbitrary::Unstructured<'u>, - schema: &'ue apollo_compiler::Schema, - ) -> Unstructured<'u, 'ue> { - Unstructured { u, schema } - } -} - -impl<'u, 'ue> Deref for Unstructured<'u, 'ue> { - type Target = arbitrary::Unstructured<'u>; - - fn deref(&self) -> &Self::Target { - &self.u - } -} - -impl<'u, 'ue> DerefMut for Unstructured<'u, 'ue> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.u - } +pub(crate) trait UnstructuredExt { + fn arbitrary_vec Result>( + &mut self, + min: usize, + max: usize, + callback: C, + ) -> Result>; } - -impl<'u, 'ue> Unstructured<'u, 'ue> { - pub(crate) fn valid(&mut self) -> Valid<'u, 'ue, '_> { - Valid(self) - } - - pub(crate) fn existing(&mut self) -> Existing<'u, 'ue, '_> { - Existing(self) - } - pub(crate) fn invalid(&mut self) -> Invalid<'u, 'ue, '_> { - Invalid(self) - } - - pub(crate) fn document<'d, 'ad>( +impl<'a> UnstructuredExt for Unstructured<'a> { + fn arbitrary_vec Result>( &mut self, - doc: &'ad mut apollo_compiler::ast::Document, - ) -> Document<'u, 'ue, '_, 'ad> { - Document { u: self, doc } - } - - pub(crate) fn schema(&self) -> &apollo_compiler::Schema { - &self.schema - } - - pub(crate) fn arbitrary_node_str(&mut self) -> Result { - Ok(NodeStr::new(self.arbitrary()?)) - } - - pub(crate) fn arbitrary_name(&mut self) -> Result { - loop { - if let Ok(name) = Name::new(self.arbitrary_node_str()?) { - return Ok(name); - } + min: usize, + max: usize, + callback: C, + ) -> Result> { + let count = self.int_in_range(min..=max)?; + let mut results = Vec::with_capacity(count); + for _ in 0..count { + results.push(callback(self)?); } + Ok(results) } +} - pub(crate) fn arbitrary_unique_name(&mut self, existing: &Vec<&Name>) -> Result { - loop { - if let Ok(name) = self.arbitrary_name() { - if !existing.contains(&&name) { - return Ok(name); - } - } - } - } +pub(crate) trait UnstructuredOption: Sized { + fn optional(self, u: &mut Unstructured) -> Result>; +} - pub(crate) fn wrap_ty(&mut self, name: Name) -> Result { - let depth = self.int_in_range(0..=10)?; - let mut ty = if self.arbitrary()? { - Type::Named(name.clone()) +impl UnstructuredOption for T { + fn optional(self, u: &mut Unstructured) -> Result> { + if u.arbitrary()? { + Ok(Some(self)) } else { - Type::NonNullNamed(name.clone()) - }; - for _ in 0..depth { - if self.arbitrary()? { - ty = Type::List(Box::new(ty)) - } else { - ty = Type::NonNullList(Box::new(ty)) - } + Ok(None) } - Ok(ty) } } + +struct Unstructured<'a> { + u: &'a mut Unstructured<'a>, +} diff --git a/crates/apollo-smith/src/next/valid.rs b/crates/apollo-smith/src/next/valid.rs deleted file mode 100644 index 3f55060ca..000000000 --- a/crates/apollo-smith/src/next/valid.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::next::unstructured::Unstructured; -use apollo_compiler::ast::{FieldDefinition, Name, ObjectTypeDefinition}; -use std::ops::{Deref, DerefMut}; - -pub(crate) struct Valid<'u, 'ue, 'n>(pub &'n mut Unstructured<'u, 'ue>); - -impl<'u, 'ue, 'n> Deref for Valid<'u, 'ue, 'n> { - type Target = Unstructured<'u, 'ue>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} -impl<'u, 'ue, 'n> DerefMut for Valid<'u, 'ue, 'n> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl<'u, 'ue, 'n> Valid<'u, 'ue, 'n> { - pub(crate) fn object_type_definition(&'n mut self) -> arbitrary::Result { - Ok(ObjectTypeDefinition { - description: self.arbitrary_node_str()?.into(), - name: self.valid().type_name()?, - implements_interfaces: vec![], - directives: Default::default(), - fields: vec![self.valid().field_definition()?.into()], - }) - } - - pub(crate) fn type_name(&mut self) -> arbitrary::Result { - let existing_type_names = self.schema().types.keys().cloned().collect::>(); - loop { - let name = self.arbitrary_name()?; - if !existing_type_names.contains(&name) { - return Ok(name); - } - } - } - pub(crate) fn field_definition(&mut self) -> arbitrary::Result { - Ok(FieldDefinition { - description: self.arbitrary_node_str()?.into(), - name: self.arbitrary_name()?, - arguments: vec![], - ty: self.existing().ty()?, - directives: Default::default(), - }) - } -} diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 945004450..9c9b49a4a 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -58,3 +58,9 @@ name = "coordinate" path = "fuzz_targets/coordinate.rs" test = false doc = false + +[[bin]] +name = "parser_next" +path = "fuzz_targets/parser_next.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/parser_next.rs b/fuzz/fuzz_targets/parser_next.rs index 54be45ce7..187144d19 100644 --- a/fuzz/fuzz_targets/parser_next.rs +++ b/fuzz/fuzz_targets/parser_next.rs @@ -1,6 +1,7 @@ #![no_main] use apollo_parser::Parser; -use apollo_rs_fuzz::{generate_valid_document, log_gql_doc}; +use apollo_rs_fuzz::{fuzz_document, generate_valid_document, log_gql_doc}; +use libfuzzer_sys::arbitrary::Unstructured; use libfuzzer_sys::fuzz_target; use log::debug; use std::panic; @@ -8,13 +9,6 @@ use std::panic; fuzz_target!(|data: &[u8]| { let _ = env_logger::try_init(); - let doc_generated = match generate_valid_document(data) { - Ok(d) => d, - Err(_err) => { - return; - } - }; - let parser = panic::catch_unwind(|| Parser::new(&doc_generated)); let parser = match parser { diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs index 72bf20b50..dcf982e21 100644 --- a/fuzz/src/lib.rs +++ b/fuzz/src/lib.rs @@ -1,3 +1,4 @@ +use apollo_compiler::Schema; use apollo_smith::DocumentBuilder; use libfuzzer_sys::arbitrary::{Result, Unstructured}; @@ -12,16 +13,6 @@ pub fn generate_valid_document(input: &[u8]) -> Result { Ok(document.into()) } -pub fn generate_document(input: &[u8]) -> Result { - drop(env_logger::try_init()); - - let mut u = Unstructured::new(input); - let gql_doc = DocumentBuilder::new(&mut u)?; - let document = gql_doc.finish(); - - Ok(document.into()) -} - /// Log the error and the document generated for these errors /// Save it into files pub fn log_gql_doc(gql_doc: &str, errors: &str) { From fddd791625321d21fb66f3a31d04c63087f25f04 Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Wed, 14 Feb 2024 09:56:45 +0000 Subject: [PATCH 07/23] Temp --- .../src/next/ast/directive_definition.rs | 6 +- crates/apollo-smith/src/next/ast/document.rs | 358 +--------------- .../src/next/ast/object_type_definition.rs | 6 +- crates/apollo-smith/src/next/common.rs | 75 ---- crates/apollo-smith/src/next/mod.rs | 4 +- .../mutations/add_directive_definition.rs | 5 +- .../mutations/add_enum_type_definition.rs | 5 +- .../add_input_object_type_definition.rs | 5 +- .../add_interface_type_definition.rs | 5 +- .../mutations/add_object_type_definition.rs | 5 +- .../next/mutations/add_schema_definition.rs | 5 +- .../mutations/add_union_type_definition.rs | 5 +- crates/apollo-smith/src/next/mutations/mod.rs | 2 +- .../src/next/mutations/remove_all_fields.rs | 2 +- crates/apollo-smith/src/next/unstructured.rs | 386 +++++++++++++++++- 15 files changed, 407 insertions(+), 467 deletions(-) delete mode 100644 crates/apollo-smith/src/next/common.rs diff --git a/crates/apollo-smith/src/next/ast/directive_definition.rs b/crates/apollo-smith/src/next/ast/directive_definition.rs index 48f25e972..ce4d1431f 100644 --- a/crates/apollo-smith/src/next/ast/directive_definition.rs +++ b/crates/apollo-smith/src/next/ast/directive_definition.rs @@ -1,9 +1,9 @@ use super::document::DocumentExt; +use crate::next::unstructured::Unstructured; use apollo_compiler::ast::{ Argument, Directive, DirectiveDefinition, DirectiveList, DirectiveLocation, Document, }; use apollo_compiler::{Node, Schema}; -use arbitrary::Unstructured; use std::ops::Deref; pub(crate) struct LocationFilter(I, DirectiveLocation); @@ -27,7 +27,6 @@ pub(crate) trait DirectiveDefinitionIterExt { fn try_collect<'a>( self, u: &mut Unstructured, - doc: &Document, schema: &Schema, ) -> arbitrary::Result where @@ -46,7 +45,6 @@ impl DirectiveDefinitionIterExt for I { fn try_collect<'a>( mut self, u: &mut Unstructured, - doc: &Document, schema: &Schema, ) -> arbitrary::Result where @@ -59,7 +57,7 @@ impl DirectiveDefinitionIterExt for I { if arg.is_required() || u.arbitrary()? { arguments.push(Node::new(Argument { name: arg.name.clone(), - value: Node::new(doc.arbitrary_value(u, arg.ty.deref(), schema)?), + value: Node::new(u.arbitrary_value(arg.ty.deref(), schema)?), })) } } diff --git a/crates/apollo-smith/src/next/ast/document.rs b/crates/apollo-smith/src/next/ast/document.rs index 6c984413f..309e2b99e 100644 --- a/crates/apollo-smith/src/next/ast/document.rs +++ b/crates/apollo-smith/src/next/ast/document.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use arbitrary::Result; -use arbitrary::Unstructured; + use paste::paste; use apollo_compiler::ast::{ @@ -18,9 +18,8 @@ use apollo_compiler::schema::ExtendedType; use apollo_compiler::{Node, NodeStr, Schema}; use crate::next::ast::definition::{DefinitionExt, DefinitionKind}; -use crate::next::common::Common; use crate::next::schema::extended_type::{ExtendedTypeExt, ExtendedTypeKind}; -use crate::next::unstructured::{UnstructuredExt, UnstructuredOption}; +use crate::next::unstructured::{Unstructured, UnstructuredExt, UnstructuredOption}; use super::super::schema::schema::SchemaExt; use super::directive_definition::DirectiveDefinitionIterExt; @@ -105,7 +104,7 @@ macro_rules! access { }; } -pub(crate) trait DocumentExt: Common { +pub(crate) trait DocumentExt { access!(OperationDefinition); access!(FragmentDefinition); access!(DirectiveDefinition); @@ -124,355 +123,6 @@ pub(crate) trait DocumentExt: Common { access!(EnumTypeExtension); access!(InputObjectTypeExtension); - fn arbitrary_schema_definition( - &self, - u: &mut Unstructured, - schema: &Schema, - ) -> Result { - Ok(SchemaDefinition { - description: self.arbitrary_node_str(u)?.optional(u)?, - directives: schema - .sample_directives(u)? - .into_iter() - .with_location(DirectiveLocation::Schema) - .try_collect(u, self.target(), schema)?, - root_operations: vec![ - Some(Node::new(( - OperationType::Query, - self.random_object_type_definition(u)?.name.clone(), - ))), - Node::new(( - OperationType::Mutation, - self.random_object_type_definition(u)?.name.clone(), - )) - .optional(u)?, - Node::new(( - OperationType::Subscription, - self.random_object_type_definition(u)?.name.clone(), - )) - .optional(u)?, - ] - .into_iter() - .filter_map(|op| op) - .collect(), - }) - } - fn arbitrary_definition_name(&self, u: &mut Unstructured, schema: &Schema) -> Result { - let existing_names = self - .target() - .definitions - .iter() - .filter_map(|d| d.name()) - .collect::>(); - loop { - let name = self.arbitrary_name(u)?; - if !existing_names.contains(&name) { - return Ok(name); - } - } - } - - fn arbitrary_object_type_definition( - &self, - u: &mut Unstructured, - schema: &Schema, - ) -> Result { - Ok(ObjectTypeDefinition { - description: self.arbitrary_node_str(u)?.optional(u)?, - name: self.arbitrary_definition_name(u, schema)?, - implements_interfaces: schema - .sample_interface_types(u)? - .iter() - .map(|i| i.name.clone()) - .collect(), - directives: schema - .sample_directives(u)? - .into_iter() - .with_location(DirectiveLocation::Object) - .try_collect(u, self.target(), schema)?, - fields: u.arbitrary_vec(0, 5, |u| { - Ok(Node::new(self.arbitrary_field_definition( - u, - schema, - DirectiveLocation::FieldDefinition, - )?)) - })?, - }) - } - - fn arbitrary_directive_definition( - &self, - u: &mut Unstructured, - schema: &Schema, - ) -> Result { - Ok(DirectiveDefinition { - description: self.arbitrary_node_str(u)?.optional(u)?, - name: self.arbitrary_definition_name(u, schema)?, - arguments: u.arbitrary_vec(0, 5, |u| { - Ok(Node::new(self.arbitrary_input_value_definition(u, schema)?)) - })?, - repeatable: u.arbitrary()?, - locations: self.arbitrary_directive_locations(u)?, - }) - } - - fn arbitrary_input_object_type_definition( - &self, - u: &mut Unstructured, - schema: &Schema, - ) -> Result { - Ok(InputObjectTypeDefinition { - description: self.arbitrary_node_str(u)?.optional(u)?, - name: self.arbitrary_name(u)?, - directives: schema - .sample_directives(u)? - .into_iter() - .with_location(DirectiveLocation::InputObject) - .try_collect(u, self.target(), schema)?, - fields: u.arbitrary_vec(0, 5, |u| { - Ok(Node::new(self.arbitrary_input_value_definition(u, schema)?)) - })?, - }) - } - - fn arbitrary_input_value_definition( - &self, - u: &mut Unstructured, - schema: &Schema, - ) -> Result { - let ty = schema - .random_type( - u, - vec![ - ExtendedTypeKind::InputObjectTypeDefinition, - ExtendedTypeKind::Scalar, - ], - )? - .ty(u)?; - let default_value = self - .arbitrary_value(u, &ty, schema)? - .optional(u)? - .map(Node::new); - Ok(InputValueDefinition { - description: self.arbitrary_node_str(u)?.optional(u)?, - name: self.unique_name(), - ty: Node::new(ty), - default_value, - directives: schema - .sample_directives(u)? - .into_iter() - .with_location(DirectiveLocation::InputFieldDefinition) - .try_collect(u, self.target(), schema)?, - }) - } - - fn arbitrary_enum_type_definition( - &self, - u: &mut Unstructured, - schema: &Schema, - ) -> Result { - Ok(EnumTypeDefinition { - description: self.arbitrary_node_str(u)?.optional(u)?, - name: self.arbitrary_definition_name(u, schema)?, - directives: schema - .sample_directives(u)? - .into_iter() - .with_location(DirectiveLocation::Enum) - .try_collect(u, self.target(), schema)?, - values: u.arbitrary_vec(0, 5, |u| { - Ok(Node::new(EnumValueDefinition { - description: self.arbitrary_node_str(u)?.optional(u)?, - value: self.arbitrary_name(u)?, - directives: schema - .sample_directives(u)? - .into_iter() - .with_location(DirectiveLocation::EnumValue) - .try_collect(u, self.target(), schema)?, - })) - })?, - }) - } - - fn arbitrary_union_type_definition( - &self, - u: &mut Unstructured, - schema: &Schema, - ) -> Result { - Ok(UnionTypeDefinition { - description: self.arbitrary_node_str(u)?.optional(u)?, - name: self.arbitrary_definition_name(u, schema)?, - directives: schema - .sample_directives(u)? - .into_iter() - .with_location(DirectiveLocation::Union) - .try_collect(u, self.target(), schema)?, - members: self - .sample_object_type_definitions(u)? - .iter() - .map(|i| i.name.clone()) - .collect(), - }) - } - - fn arbitrary_interface_type_definition( - &self, - u: &mut Unstructured, - schema: &Schema, - ) -> Result { - Ok(InterfaceTypeDefinition { - description: self.arbitrary_node_str(u)?.optional(u)?, - name: self.arbitrary_definition_name(u, schema)?, - implements_interfaces: self - .sample_interface_type_definitions(u)? - .iter() - .map(|interface| interface.name.clone()) - .collect(), - directives: schema - .sample_directives(u)? - .into_iter() - .with_location(DirectiveLocation::Interface) - .try_collect(u, self.target(), schema)?, - fields: u.arbitrary_vec(0, 5, |u| { - Ok(Node::new(self.arbitrary_field_definition( - u, - schema, - DirectiveLocation::InputFieldDefinition, - )?)) - })?, - }) - } - - fn arbitrary_field_definition( - &self, - u: &mut Unstructured, - schema: &Schema, - directive_location: DirectiveLocation, - ) -> Result { - Ok(FieldDefinition { - description: self.arbitrary_node_str(u)?.optional(u)?, - name: self.unique_name(), - arguments: u.arbitrary_vec(0, 5, |u| { - Ok(Node::new(self.arbitrary_input_value_definition(u, schema)?)) - })?, - ty: schema - .random_type( - u, - vec![ - ExtendedTypeKind::Scalar, - ExtendedTypeKind::Object, - ExtendedTypeKind::Enum, - ExtendedTypeKind::Union, - ExtendedTypeKind::Interface, - ], - )? - .ty(u)?, - directives: schema - .sample_directives(u)? - .into_iter() - .with_location(directive_location) - .try_collect(u, self.target(), schema)?, - }) - } - - fn arbitrary_value(&self, u: &mut Unstructured, ty: &Type, schema: &Schema) -> Result { - match ty { - Type::Named(ty) => { - if u.arbitrary()? { - self.arbitrary_value(u, &Type::NonNullNamed(ty.clone()), schema) - } else { - Ok(Value::Null) - } - } - Type::List(ty) => { - if u.arbitrary()? { - Ok(Value::List(u.arbitrary_vec(0, 5, |u| { - Ok(Node::new(self.arbitrary_value(u, ty, schema)?)) - })?)) - } else { - Ok(Value::Null) - } - } - Type::NonNullNamed(ty) => match schema.types.get(ty).expect("type must exist") { - ExtendedType::Scalar(ty) => { - if ty.name == "Int" { - Ok(Value::from(u.arbitrary::()?)) - } else if ty.name == "Float" { - Ok(Value::from(u.arbitrary::()?)) - } else if ty.name == "Boolean" { - Ok(Value::Boolean(u.arbitrary()?)) - } else { - Ok(Value::String(self.arbitrary_node_str(u)?)) - } - } - ExtendedType::Object(ty) => { - let mut values = Vec::new(); - for (name, definition) in &ty.fields { - values.push(( - name.clone(), - Node::new(self.arbitrary_value(u, &definition.ty, schema)?), - )); - } - Ok(Value::Object(values)) - } - ExtendedType::Enum(ty) => { - let values = ty - .values - .iter() - .map(|(name, v)| &v.value) - .collect::>(); - if values.is_empty() { - panic!("enum must have at least one value") - } else { - Ok(Value::Enum((*u.choose(&values)?).clone())) - } - } - ExtendedType::InputObject(ty) => { - let mut values = Vec::new(); - for (name, definition) in &ty.fields { - values.push(( - name.clone(), - Node::new(self.arbitrary_value(u, &definition.ty, schema)?), - )); - } - Ok(Value::Object(values)) - } - _ => { - panic!("type must be a scalar, object, enum or input object") - } - }, - Type::NonNullList(ty) => Ok(Value::List(u.arbitrary_vec(0, 5, |u| { - Ok(Node::new(self.arbitrary_value(u, ty, schema)?)) - })?)), - } - } - - fn random_definition( - &self, - u: &mut Unstructured, - types: Vec, - ) -> Result<&Definition> { - let definitions = self - .target() - .definitions - .iter() - .filter(|d| types.iter().any(|t| t.matches(*d))) - .collect::>(); - Ok(u.choose(definitions.as_slice()).map_err(|e| { - if let arbitrary::Error::EmptyChoose = e { - panic!("no existing definitions of types {:?}", types) - } else { - e - } - })?) - } - - fn definition(&self, name: &Name) -> Option<&Definition> { - self.target() - .definitions - .iter() - .find(|d| d.name() == Some(&name)) - } - fn target(&self) -> &Document; fn target_mut(&mut self) -> &mut Document; } @@ -485,5 +135,3 @@ impl DocumentExt for Document { self } } - -impl Common for Document {} diff --git a/crates/apollo-smith/src/next/ast/object_type_definition.rs b/crates/apollo-smith/src/next/ast/object_type_definition.rs index 7a13ad883..25053eb8c 100644 --- a/crates/apollo-smith/src/next/ast/object_type_definition.rs +++ b/crates/apollo-smith/src/next/ast/object_type_definition.rs @@ -3,9 +3,7 @@ use arbitrary::Unstructured; use apollo_compiler::ast::ObjectTypeDefinition; use apollo_compiler::Node; -use crate::next::common::Common; - -pub(crate) trait ObjectTypeDefinitionExt: Common { +pub(crate) trait ObjectTypeDefinitionExt { field_access!(); fn target(&self) -> &ObjectTypeDefinition; @@ -20,5 +18,3 @@ impl ObjectTypeDefinitionExt for ObjectTypeDefinition { self } } - -impl Common for ObjectTypeDefinition {} diff --git a/crates/apollo-smith/src/next/common.rs b/crates/apollo-smith/src/next/common.rs deleted file mode 100644 index 5142c04e0..000000000 --- a/crates/apollo-smith/src/next/common.rs +++ /dev/null @@ -1,75 +0,0 @@ -//static Atomic integer -use std::sync::atomic::{AtomicUsize, Ordering}; - -use arbitrary::Unstructured; - -use apollo_compiler::ast::{DirectiveLocation, Name}; -use apollo_compiler::NodeStr; - -static COUNTER: AtomicUsize = AtomicUsize::new(0); - -pub(crate) trait Common { - fn unique_name(&self) -> Name { - Name::new(NodeStr::new(&format!( - "f{}", - COUNTER.fetch_add(1, Ordering::SeqCst) - ))) - .expect("valid name") - } - fn arbitrary_name(&self, u: &mut Unstructured) -> arbitrary::Result { - loop { - let s: String = u.arbitrary()?; - let idx = s - .char_indices() - .nth(10) - .map(|(s, _c)| s) - .unwrap_or_else(|| s.len()); - if let Ok(name) = Name::new(s[..idx].to_string()) { - return Ok(name); - } - } - } - - fn arbitrary_node_str(&self, u: &mut Unstructured) -> arbitrary::Result { - let s: String = u.arbitrary()?; - let idx = s - .char_indices() - .nth(10) - .map(|(s, _c)| s) - .unwrap_or_else(|| s.len()); - Ok(NodeStr::new(&s[..idx])) - } - - fn arbitrary_directive_locations( - &self, - u: &mut Unstructured, - ) -> arbitrary::Result> { - let mut locations = Vec::new(); - for _ in 0..u.int_in_range(1..=5)? { - locations.push( - u.choose(&[ - DirectiveLocation::Query, - DirectiveLocation::Mutation, - DirectiveLocation::Subscription, - DirectiveLocation::Field, - DirectiveLocation::FragmentDefinition, - DirectiveLocation::FragmentSpread, - DirectiveLocation::InlineFragment, - DirectiveLocation::Schema, - DirectiveLocation::Scalar, - DirectiveLocation::Object, - DirectiveLocation::FieldDefinition, - DirectiveLocation::ArgumentDefinition, - DirectiveLocation::Interface, - DirectiveLocation::Union, - DirectiveLocation::Enum, - DirectiveLocation::EnumValue, - DirectiveLocation::InputObject, - DirectiveLocation::InputFieldDefinition, - ])? - .clone(), - ); - } - Ok(locations) - } -} diff --git a/crates/apollo-smith/src/next/mod.rs b/crates/apollo-smith/src/next/mod.rs index 2a169c4f1..fd01d3036 100644 --- a/crates/apollo-smith/src/next/mod.rs +++ b/crates/apollo-smith/src/next/mod.rs @@ -1,8 +1,7 @@ use std::any::type_name; use std::path::PathBuf; -use arbitrary::Unstructured; - +use crate::next::unstructured::Unstructured; use apollo_compiler::ast::Document; use apollo_compiler::validation::WithErrors; use apollo_compiler::Schema; @@ -53,7 +52,6 @@ macro_rules! field_access { }; } mod ast; -mod common; mod mutations; mod schema; mod unstructured; diff --git a/crates/apollo-smith/src/next/mutations/add_directive_definition.rs b/crates/apollo-smith/src/next/mutations/add_directive_definition.rs index e1b2317b1..d8f6a3f0b 100644 --- a/crates/apollo-smith/src/next/mutations/add_directive_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_directive_definition.rs @@ -1,10 +1,9 @@ -use arbitrary::Unstructured; - use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; use crate::next::ast::document::DocumentExt; use crate::next::mutations::Mutation; +use crate::next::unstructured::Unstructured; pub(crate) struct AddDirectiveDefinition; impl Mutation for AddDirectiveDefinition { @@ -16,7 +15,7 @@ impl Mutation for AddDirectiveDefinition { ) -> arbitrary::Result<()> { doc.definitions .push(Definition::DirectiveDefinition(Node::new( - doc.arbitrary_directive_definition(u, schema)?, + u.arbitrary_directive_definition(schema)?, ))); Ok(()) diff --git a/crates/apollo-smith/src/next/mutations/add_enum_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_enum_type_definition.rs index 2d545f110..8dd16676a 100644 --- a/crates/apollo-smith/src/next/mutations/add_enum_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_enum_type_definition.rs @@ -1,10 +1,9 @@ -use arbitrary::Unstructured; - use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; use crate::next::ast::document::DocumentExt; use crate::next::mutations::Mutation; +use crate::next::unstructured::Unstructured; pub(crate) struct AddEnumTypeDefinition; impl Mutation for AddEnumTypeDefinition { @@ -16,7 +15,7 @@ impl Mutation for AddEnumTypeDefinition { ) -> arbitrary::Result<()> { doc.definitions .push(Definition::EnumTypeDefinition(Node::new( - doc.arbitrary_enum_type_definition(u, schema)?, + u.arbitrary_enum_type_definition(schema)?, ))); Ok(()) diff --git a/crates/apollo-smith/src/next/mutations/add_input_object_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_input_object_type_definition.rs index 03d396797..646588e5a 100644 --- a/crates/apollo-smith/src/next/mutations/add_input_object_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_input_object_type_definition.rs @@ -1,10 +1,9 @@ -use arbitrary::Unstructured; - use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; use crate::next::ast::document::DocumentExt; use crate::next::mutations::Mutation; +use crate::next::unstructured::Unstructured; pub(crate) struct AddInputObjectTypeDefinition; impl Mutation for AddInputObjectTypeDefinition { @@ -16,7 +15,7 @@ impl Mutation for AddInputObjectTypeDefinition { ) -> arbitrary::Result<()> { doc.definitions .push(Definition::InputObjectTypeDefinition(Node::new( - doc.arbitrary_input_object_type_definition(u, schema)?, + u.arbitrary_input_object_type_definition(schema)?, ))); Ok(()) } diff --git a/crates/apollo-smith/src/next/mutations/add_interface_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_interface_type_definition.rs index a2fb135c1..1cca3e287 100644 --- a/crates/apollo-smith/src/next/mutations/add_interface_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_interface_type_definition.rs @@ -1,10 +1,9 @@ -use arbitrary::Unstructured; - use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; use crate::next::ast::document::DocumentExt; use crate::next::mutations::Mutation; +use crate::next::unstructured::Unstructured; pub(crate) struct AddInterfaceTypeDefinition; impl Mutation for AddInterfaceTypeDefinition { @@ -16,7 +15,7 @@ impl Mutation for AddInterfaceTypeDefinition { ) -> arbitrary::Result<()> { doc.definitions .push(Definition::InterfaceTypeDefinition(Node::new( - doc.arbitrary_interface_type_definition(u, schema)?, + u.arbitrary_interface_type_definition(schema)?, ))); Ok(()) diff --git a/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs index 2eb84ab67..088c58a11 100644 --- a/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs @@ -1,10 +1,9 @@ -use arbitrary::Unstructured; - use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; use crate::next::ast::document::DocumentExt; use crate::next::mutations::Mutation; +use crate::next::unstructured::Unstructured; pub(crate) struct AddObjectTypeDefinition; impl Mutation for AddObjectTypeDefinition { @@ -16,7 +15,7 @@ impl Mutation for AddObjectTypeDefinition { ) -> arbitrary::Result<()> { doc.definitions .push(Definition::ObjectTypeDefinition(Node::new( - doc.arbitrary_object_type_definition(u, schema)?, + u.arbitrary_object_type_definition(schema)?, ))); Ok(()) } diff --git a/crates/apollo-smith/src/next/mutations/add_schema_definition.rs b/crates/apollo-smith/src/next/mutations/add_schema_definition.rs index 00035c613..2a9e9a405 100644 --- a/crates/apollo-smith/src/next/mutations/add_schema_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_schema_definition.rs @@ -1,10 +1,9 @@ -use arbitrary::Unstructured; - use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; use crate::next::ast::document::DocumentExt; use crate::next::mutations::Mutation; +use crate::next::unstructured::Unstructured; pub(crate) struct AddSchemaDefiniton; impl Mutation for AddSchemaDefiniton { @@ -16,7 +15,7 @@ impl Mutation for AddSchemaDefiniton { ) -> arbitrary::Result<()> { // If the document already has a schema definition, we don't need to add another one doc.definitions.push(Definition::SchemaDefinition(Node::new( - doc.arbitrary_schema_definition(u, schema)?, + u.arbitrary_schema_definition(schema)?, ))); Ok(()) diff --git a/crates/apollo-smith/src/next/mutations/add_union_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_union_type_definition.rs index 5c33958c7..8386d3eb1 100644 --- a/crates/apollo-smith/src/next/mutations/add_union_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_union_type_definition.rs @@ -1,10 +1,9 @@ -use arbitrary::Unstructured; - use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; use crate::next::ast::document::DocumentExt; use crate::next::mutations::Mutation; +use crate::next::unstructured::Unstructured; pub(crate) struct AddUnionTypeDefinition; impl Mutation for AddUnionTypeDefinition { @@ -16,7 +15,7 @@ impl Mutation for AddUnionTypeDefinition { ) -> arbitrary::Result<()> { doc.definitions .push(Definition::UnionTypeDefinition(Node::new( - doc.arbitrary_union_type_definition(u, schema)?, + u.arbitrary_union_type_definition(schema)?, ))); Ok(()) diff --git a/crates/apollo-smith/src/next/mutations/mod.rs b/crates/apollo-smith/src/next/mutations/mod.rs index 7e7aaf15f..4cdc8fe86 100644 --- a/crates/apollo-smith/src/next/mutations/mod.rs +++ b/crates/apollo-smith/src/next/mutations/mod.rs @@ -1,4 +1,3 @@ -use arbitrary::Unstructured; use std::any::type_name; use crate::next::mutations::add_directive_definition::AddDirectiveDefinition; @@ -10,6 +9,7 @@ use apollo_compiler::Schema; use crate::next::mutations::add_object_type_definition::AddObjectTypeDefinition; use crate::next::mutations::add_schema_definition::AddSchemaDefiniton; use crate::next::mutations::add_union_type_definition::AddUnionTypeDefinition; +use crate::next::unstructured::Unstructured; mod add_directive_definition; mod add_enum_type_definition; diff --git a/crates/apollo-smith/src/next/mutations/remove_all_fields.rs b/crates/apollo-smith/src/next/mutations/remove_all_fields.rs index dff3a4c20..0581ec13a 100644 --- a/crates/apollo-smith/src/next/mutations/remove_all_fields.rs +++ b/crates/apollo-smith/src/next/mutations/remove_all_fields.rs @@ -1,7 +1,7 @@ use crate::next::mutations::Mutation; -use arbitrary::Unstructured; use crate::next::ast::document::DocumentExt; +use crate::next::unstructured::Unstructured; use apollo_compiler::ast::Document; use apollo_compiler::Schema; diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index 814daa097..b4b0a0079 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -1,4 +1,16 @@ +use crate::next::ast::directive_definition::DirectiveDefinitionIterExt; +use crate::next::schema::extended_type::{ExtendedTypeExt, ExtendedTypeKind}; +use crate::next::schema::schema::SchemaExt; +use apollo_compiler::ast::{ + DirectiveDefinition, DirectiveLocation, EnumTypeDefinition, EnumValueDefinition, + FieldDefinition, InputObjectTypeDefinition, InputValueDefinition, InterfaceTypeDefinition, + Name, ObjectTypeDefinition, OperationType, SchemaDefinition, Type, UnionTypeDefinition, Value, +}; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::{Node, NodeStr, Schema}; use arbitrary::Result; +use std::ops::{Deref, DerefMut}; +use std::sync::atomic::Ordering; pub(crate) trait UnstructuredExt { fn arbitrary_vec Result>( @@ -38,6 +50,376 @@ impl UnstructuredOption for T { } } -struct Unstructured<'a> { - u: &'a mut Unstructured<'a>, +pub(crate) struct Unstructured<'a> { + u: arbitrary::Unstructured<'a>, + counter: usize, +} + +impl Unstructured<'_> { + pub(crate) fn new<'a>(data: &'a [u8]) -> Unstructured<'a> { + Unstructured { + u: arbitrary::Unstructured::new(data), + counter: 0, + } + } + + pub(crate) fn unique_name(&mut self) -> Name { + self.counter = self.counter + 1; + Name::new(NodeStr::new(&format!("f{}", self.counter))).expect("valid name") + } + + pub(crate) fn arbitrary_node_str(&mut self) -> Result { + let s: String = self.arbitrary()?; + let idx = s + .char_indices() + .nth(10) + .map(|(s, _c)| s) + .unwrap_or_else(|| s.len()); + Ok(NodeStr::new(&s[..idx])) + } + + pub(crate) fn arbitrary_directive_locations( + &mut self, + ) -> arbitrary::Result> { + let mut locations = Vec::new(); + for _ in 0..self.int_in_range(1..=5)? { + locations.push( + self.choose(&[ + DirectiveLocation::Query, + DirectiveLocation::Mutation, + DirectiveLocation::Subscription, + DirectiveLocation::Field, + DirectiveLocation::FragmentDefinition, + DirectiveLocation::FragmentSpread, + DirectiveLocation::InlineFragment, + DirectiveLocation::Schema, + DirectiveLocation::Scalar, + DirectiveLocation::Object, + DirectiveLocation::FieldDefinition, + DirectiveLocation::ArgumentDefinition, + DirectiveLocation::Interface, + DirectiveLocation::Union, + DirectiveLocation::Enum, + DirectiveLocation::EnumValue, + DirectiveLocation::InputObject, + DirectiveLocation::InputFieldDefinition, + ])? + .clone(), + ); + } + Ok(locations) + } + + pub(crate) fn arbitrary_schema_definition( + &mut self, + schema: &Schema, + ) -> Result { + Ok(SchemaDefinition { + description: self.arbitrary_node_str()?.optional(self)?, + directives: schema + .sample_directives(self)? + .into_iter() + .with_location(DirectiveLocation::Schema) + .try_collect(self, schema)?, + root_operations: vec![ + Some(Node::new(( + OperationType::Query, + schema.random_object_type(self)?.name.clone(), + ))), + Node::new(( + OperationType::Mutation, + schema.random_object_type(self)?.name.clone(), + )) + .optional(self)?, + Node::new(( + OperationType::Subscription, + schema.random_object_type(self)?.name.clone(), + )) + .optional(self)?, + ] + .into_iter() + .filter_map(|op| op) + .collect(), + }) + } + + pub(crate) fn arbitrary_object_type_definition( + &mut self, + schema: &Schema, + ) -> Result { + Ok(ObjectTypeDefinition { + description: self.arbitrary_node_str()?.optional(self)?, + name: self.unique_name(), + implements_interfaces: schema + .sample_interface_types(self)? + .iter() + .map(|i| i.name.clone()) + .collect(), + directives: schema + .sample_directives(self)? + .into_iter() + .with_location(DirectiveLocation::Object) + .try_collect(self, schema)?, + fields: self.arbitrary_vec(0, 5, |u| { + Ok(Node::new(u.arbitrary_field_definition( + schema, + DirectiveLocation::FieldDefinition, + )?)) + })?, + }) + } + + pub(crate) fn arbitrary_directive_definition( + &mut self, + schema: &Schema, + ) -> Result { + Ok(DirectiveDefinition { + description: self.arbitrary_node_str()?.optional(self)?, + name: self.unique_name(), + arguments: self.arbitrary_vec(0, 5, |u| { + Ok(Node::new(u.arbitrary_input_value_definition(schema)?)) + })?, + repeatable: self.arbitrary()?, + locations: self.arbitrary_directive_locations()?, + }) + } + + pub(crate) fn arbitrary_input_object_type_definition( + &mut self, + schema: &Schema, + ) -> Result { + Ok(InputObjectTypeDefinition { + description: self.arbitrary_node_str()?.optional(self)?, + name: self.unique_name(), + directives: schema + .sample_directives(self)? + .into_iter() + .with_location(DirectiveLocation::InputObject) + .try_collect(self, schema)?, + fields: self.arbitrary_vec(0, 5, |u| { + Ok(Node::new(u.arbitrary_input_value_definition(schema)?)) + })?, + }) + } + + pub(crate) fn arbitrary_input_value_definition( + &mut self, + schema: &Schema, + ) -> Result { + let ty = schema + .random_type( + self, + vec![ + ExtendedTypeKind::InputObjectTypeDefinition, + ExtendedTypeKind::Scalar, + ], + )? + .ty(self)?; + let default_value = self + .arbitrary_value(&ty, schema)? + .optional(self)? + .map(Node::new); + Ok(InputValueDefinition { + description: self.arbitrary_node_str()?.optional(self)?, + name: self.unique_name(), + ty: Node::new(ty), + default_value, + directives: schema + .sample_directives(self)? + .into_iter() + .with_location(DirectiveLocation::InputFieldDefinition) + .try_collect(self, schema)?, + }) + } + + pub(crate) fn arbitrary_enum_type_definition( + &mut self, + schema: &Schema, + ) -> Result { + Ok(EnumTypeDefinition { + description: self.arbitrary_node_str()?.optional(self)?, + name: self.unique_name(), + directives: schema + .sample_directives(self)? + .into_iter() + .with_location(DirectiveLocation::Enum) + .try_collect(self, schema)?, + values: self.arbitrary_vec(0, 5, |u| { + Ok(Node::new(EnumValueDefinition { + description: u.arbitrary_node_str()?.optional(u)?, + value: u.unique_name(), + directives: schema + .sample_directives(u)? + .into_iter() + .with_location(DirectiveLocation::EnumValue) + .try_collect(u, schema)?, + })) + })?, + }) + } + + pub(crate) fn arbitrary_union_type_definition( + &mut self, + schema: &Schema, + ) -> Result { + Ok(UnionTypeDefinition { + description: self.arbitrary_node_str()?.optional(self)?, + name: self.unique_name(), + directives: schema + .sample_directives(self)? + .into_iter() + .with_location(DirectiveLocation::Union) + .try_collect(self, schema)?, + members: schema + .sample_object_types(self)? + .iter() + .map(|i| i.name.clone()) + .collect(), + }) + } + + pub(crate) fn arbitrary_interface_type_definition( + &mut self, + schema: &Schema, + ) -> Result { + Ok(InterfaceTypeDefinition { + description: self.arbitrary_node_str()?.optional(self)?, + name: self.unique_name(), + implements_interfaces: schema + .sample_interface_types(self)? + .iter() + .map(|interface| interface.name.clone()) + .collect(), + directives: schema + .sample_directives(self)? + .into_iter() + .with_location(DirectiveLocation::Interface) + .try_collect(self, schema)?, + fields: self.arbitrary_vec(0, 5, |u| { + Ok(Node::new(u.arbitrary_field_definition( + schema, + DirectiveLocation::InputFieldDefinition, + )?)) + })?, + }) + } + + pub(crate) fn arbitrary_field_definition( + &mut self, + schema: &Schema, + directive_location: DirectiveLocation, + ) -> Result { + Ok(FieldDefinition { + description: self.arbitrary_node_str()?.optional(self)?, + name: self.unique_name(), + arguments: self.arbitrary_vec(0, 5, |u| { + Ok(Node::new(u.arbitrary_input_value_definition(schema)?)) + })?, + ty: schema + .random_type( + self, + vec![ + ExtendedTypeKind::Scalar, + ExtendedTypeKind::Object, + ExtendedTypeKind::Enum, + ExtendedTypeKind::Union, + ExtendedTypeKind::Interface, + ], + )? + .ty(self)?, + directives: schema + .sample_directives(self)? + .into_iter() + .with_location(directive_location) + .try_collect(self, schema)?, + }) + } + + pub(crate) fn arbitrary_value(&mut self, ty: &Type, schema: &Schema) -> Result { + match ty { + Type::Named(ty) => { + if self.arbitrary()? { + self.arbitrary_value(&Type::NonNullNamed(ty.clone()), schema) + } else { + Ok(Value::Null) + } + } + Type::List(ty) => { + if self.arbitrary()? { + Ok(Value::List(self.arbitrary_vec(0, 5, |u| { + Ok(Node::new(u.arbitrary_value(ty, schema)?)) + })?)) + } else { + Ok(Value::Null) + } + } + Type::NonNullNamed(ty) => match schema.types.get(ty).expect("type must exist") { + ExtendedType::Scalar(ty) => { + if ty.name == "Int" { + Ok(Value::from(self.arbitrary::()?)) + } else if ty.name == "Float" { + Ok(Value::from(self.arbitrary::()?)) + } else if ty.name == "Boolean" { + Ok(Value::Boolean(self.arbitrary()?)) + } else { + Ok(Value::String(self.arbitrary_node_str()?)) + } + } + ExtendedType::Object(ty) => { + let mut values = Vec::new(); + for (name, definition) in &ty.fields { + values.push(( + name.clone(), + Node::new(self.arbitrary_value(&definition.ty, schema)?), + )); + } + Ok(Value::Object(values)) + } + ExtendedType::Enum(ty) => { + let values = ty + .values + .iter() + .map(|(name, v)| &v.value) + .collect::>(); + if values.is_empty() { + panic!("enum must have at least one value") + } else { + Ok(Value::Enum((*self.choose(&values)?).clone())) + } + } + ExtendedType::InputObject(ty) => { + let mut values = Vec::new(); + for (name, definition) in &ty.fields { + values.push(( + name.clone(), + Node::new(self.arbitrary_value(&definition.ty, schema)?), + )); + } + Ok(Value::Object(values)) + } + _ => { + panic!("type must be a scalar, object, enum or input object") + } + }, + Type::NonNullList(ty) => { + Ok(Value::List(self.arbitrary_vec(0, 5, |u| { + Ok(Node::new(u.arbitrary_value(ty, schema)?)) + })?)) + } + } + } +} + +impl<'a> Deref for Unstructured<'a> { + type Target = arbitrary::Unstructured<'a>; + + fn deref(&self) -> &Self::Target { + &self.u + } +} + +impl<'a> DerefMut for Unstructured<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.u + } } From 9e092188c954d502776f46d0150a8726823d2e7b Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Wed, 14 Feb 2024 10:20:49 +0000 Subject: [PATCH 08/23] Temp --- crates/apollo-smith/src/next/ast/document.rs | 27 +++++------------ crates/apollo-smith/src/next/mod.rs | 11 +++---- crates/apollo-smith/src/next/test.graphql | 7 ----- crates/apollo-smith/src/next/unstructured.rs | 2 +- fuzz/fuzz_targets/parser_next.rs | 32 ++------------------ fuzz/src/lib.rs | 9 ++++++ 6 files changed, 25 insertions(+), 63 deletions(-) delete mode 100644 crates/apollo-smith/src/next/test.graphql diff --git a/crates/apollo-smith/src/next/ast/document.rs b/crates/apollo-smith/src/next/ast/document.rs index 309e2b99e..dd8634b1b 100644 --- a/crates/apollo-smith/src/next/ast/document.rs +++ b/crates/apollo-smith/src/next/ast/document.rs @@ -1,28 +1,15 @@ -use std::collections::HashSet; - -use arbitrary::Result; - use paste::paste; use apollo_compiler::ast::{ - Definition, DirectiveDefinition, DirectiveLocation, Document, EnumTypeDefinition, - EnumTypeExtension, EnumValueDefinition, FieldDefinition, FragmentDefinition, - InputObjectTypeDefinition, InputObjectTypeExtension, InputValueDefinition, - InterfaceTypeDefinition, InterfaceTypeExtension, Name, ObjectTypeDefinition, - ObjectTypeExtension, OperationDefinition, OperationType, ScalarTypeDefinition, - ScalarTypeExtension, SchemaDefinition, SchemaExtension, Type, UnionTypeDefinition, - UnionTypeExtension, Value, + Definition, DirectiveDefinition, Document, EnumTypeDefinition, EnumTypeExtension, + FragmentDefinition, InputObjectTypeDefinition, InputObjectTypeExtension, + InterfaceTypeDefinition, InterfaceTypeExtension, ObjectTypeDefinition, ObjectTypeExtension, + OperationDefinition, ScalarTypeDefinition, ScalarTypeExtension, SchemaDefinition, + SchemaExtension, UnionTypeDefinition, UnionTypeExtension, }; -use apollo_compiler::executable::DirectiveList; -use apollo_compiler::schema::ExtendedType; -use apollo_compiler::{Node, NodeStr, Schema}; - -use crate::next::ast::definition::{DefinitionExt, DefinitionKind}; -use crate::next::schema::extended_type::{ExtendedTypeExt, ExtendedTypeKind}; -use crate::next::unstructured::{Unstructured, UnstructuredExt, UnstructuredOption}; +use apollo_compiler::Node; -use super::super::schema::schema::SchemaExt; -use super::directive_definition::DirectiveDefinitionIterExt; +use crate::next::unstructured::Unstructured; /// Macro to create accessors for definitions macro_rules! access { diff --git a/crates/apollo-smith/src/next/mod.rs b/crates/apollo-smith/src/next/mod.rs index fd01d3036..2e8979917 100644 --- a/crates/apollo-smith/src/next/mod.rs +++ b/crates/apollo-smith/src/next/mod.rs @@ -73,14 +73,15 @@ pub enum Error { pub fn generate_schema_document(input: &[u8]) -> Result { let mut u = Unstructured::new(input); - println!("starting"); let mut doc = Document::parse( "type Query { me: String }".to_string(), PathBuf::from("synthetic"), ) .map_err(Error::Parse)?; // Start with a minimal schema - println!("parsed initial"); let mutations = mutations::all_mutations(); + for mutation in &mutations { + println!("mutation {}", mutation.type_name()); + } let mut schema = doc.to_schema().expect("initial schema must be valid"); for n in 0..1000 { println!("iteration: {}", n); @@ -93,8 +94,6 @@ pub fn generate_schema_document(input: &[u8]) -> Result { Document::parse(doc.to_string(), PathBuf::from("synthetic")).map_err(Error::Parse)?; // Now let's validate that the schema says it's OK - println!("{}", doc.to_string()); - match (mutation.is_valid(), doc.to_schema_validate()) { (true, Ok(new_schema)) => { schema = new_schema.into_inner(); @@ -107,7 +106,7 @@ pub fn generate_schema_document(input: &[u8]) -> Result { return Err(Error::ExpectedValidationFail(doc)); } (false, Err(_)) => { - // Validation was expected to fail, we can't continue + // Validation was expected to fail, we can't continue as we mutated the doc return Ok(doc); } } @@ -130,7 +129,7 @@ mod test { #[test] fn test() { - let input = b"293ur928jff029jf0293f"; + let input = b"293ur928jff029jf0293ff20983nr0v243ucm8unr4pv938uavtnpb98aun34vtr98aun23vr892auna98r3unv298ua43arp9vu32a3p538umq2v4098cutrnfavwynr9ha28pbz9vuwrA29P38AU[R09UMA2[0893U5NC9A8P3NRV"; match generate_schema_document(input) { Ok(_) => {} Err(e) => { diff --git a/crates/apollo-smith/src/next/test.graphql b/crates/apollo-smith/src/next/test.graphql deleted file mode 100644 index 10640c312..000000000 --- a/crates/apollo-smith/src/next/test.graphql +++ /dev/null @@ -1,7 +0,0 @@ - -type Query { -} - -type Foo { - -} diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index b4b0a0079..3f214db42 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -160,7 +160,7 @@ impl Unstructured<'_> { .into_iter() .with_location(DirectiveLocation::Object) .try_collect(self, schema)?, - fields: self.arbitrary_vec(0, 5, |u| { + fields: self.arbitrary_vec(1, 5, |u| { Ok(Node::new(u.arbitrary_field_definition( schema, DirectiveLocation::FieldDefinition, diff --git a/fuzz/fuzz_targets/parser_next.rs b/fuzz/fuzz_targets/parser_next.rs index 187144d19..2a7c5fc76 100644 --- a/fuzz/fuzz_targets/parser_next.rs +++ b/fuzz/fuzz_targets/parser_next.rs @@ -1,6 +1,6 @@ #![no_main] use apollo_parser::Parser; -use apollo_rs_fuzz::{fuzz_document, generate_valid_document, log_gql_doc}; +use apollo_rs_fuzz::{generate_valid_document, log_gql_doc}; use libfuzzer_sys::arbitrary::Unstructured; use libfuzzer_sys::fuzz_target; use log::debug; @@ -9,33 +9,7 @@ use std::panic; fuzz_target!(|data: &[u8]| { let _ = env_logger::try_init(); - let parser = panic::catch_unwind(|| Parser::new(&doc_generated)); - - let parser = match parser { - Err(err) => { - panic!("error {err:?}"); - } - Ok(p) => p, - }; - debug!("======= DOCUMENT ======="); - debug!("{}", doc_generated); - debug!("========================"); - - let tree = parser.parse(); - // early return if the parser detected an error - let mut should_panic = false; - if tree.errors().len() > 0 { - should_panic = true; - let errors = tree - .errors() - .map(|err| err.message()) - .collect::>() - .join("\n"); - debug!("Parser errors ========== \n{:?}", errors); - debug!("========================"); - log_gql_doc(&doc_generated, &errors); - } - if should_panic { - panic!("error detected"); + if let Err(e) = apollo_rs_fuzz::generate_schema_document(data) { + log::info!("error: {:?}", e); } }); diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs index dcf982e21..a02b58c25 100644 --- a/fuzz/src/lib.rs +++ b/fuzz/src/lib.rs @@ -13,6 +13,15 @@ pub fn generate_valid_document(input: &[u8]) -> Result { Ok(document.into()) } +pub fn generate_schema_document( + input: &[u8], +) -> std::result::Result<(), apollo_smith::next::Error> { + drop(env_logger::try_init()); + + apollo_smith::next::generate_schema_document(input)?; + Ok(()) +} + /// Log the error and the document generated for these errors /// Save it into files pub fn log_gql_doc(gql_doc: &str, errors: &str) { From 26a98813e69cceb65f722ca7956b54e92221bba5 Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Fri, 16 Feb 2024 08:23:30 +0000 Subject: [PATCH 09/23] Temp --- .../apollo-smith/src/next/ast/definition.rs | 4 +- .../src/next/ast/directive_definition.rs | 11 +- crates/apollo-smith/src/next/ast/document.rs | 69 +++-- crates/apollo-smith/src/next/ast/mod.rs | 93 +++++- .../src/next/ast/object_type_definition.rs | 20 -- crates/apollo-smith/src/next/mod.rs | 216 +++++++------ .../mutations/add_directive_definition.rs | 5 +- .../mutations/add_enum_type_definition.rs | 5 +- .../add_input_object_type_definition.rs | 5 +- .../add_interface_type_definition.rs | 5 +- .../mutations/add_object_type_definition.rs | 5 +- .../mutations/add_operation_definition.rs | 25 ++ .../next/mutations/add_schema_definition.rs | 5 +- .../mutations/add_union_type_definition.rs | 5 +- crates/apollo-smith/src/next/mutations/mod.rs | 24 +- .../src/next/mutations/remove_all_fields.rs | 30 +- .../next/mutations/remove_required_field.rs | 63 ++++ .../src/next/schema/extended_type.rs | 5 +- crates/apollo-smith/src/next/schema/mod.rs | 89 ++++++ .../src/next/schema/object_type.rs | 17 + crates/apollo-smith/src/next/schema/schema.rs | 28 +- crates/apollo-smith/src/next/unstructured.rs | 290 ++++++++++++------ fuzz/fuzz_targets/parser_next.rs | 63 +++- fuzz/src/lib.rs | 8 +- 24 files changed, 814 insertions(+), 276 deletions(-) delete mode 100644 crates/apollo-smith/src/next/ast/object_type_definition.rs create mode 100644 crates/apollo-smith/src/next/mutations/add_operation_definition.rs create mode 100644 crates/apollo-smith/src/next/mutations/remove_required_field.rs create mode 100644 crates/apollo-smith/src/next/schema/object_type.rs diff --git a/crates/apollo-smith/src/next/ast/definition.rs b/crates/apollo-smith/src/next/ast/definition.rs index 33901500d..a8d36c1e1 100644 --- a/crates/apollo-smith/src/next/ast/definition.rs +++ b/crates/apollo-smith/src/next/ast/definition.rs @@ -1,7 +1,7 @@ -use apollo_compiler::ast::{Definition, InputObjectTypeDefinition, Name, Type}; -use apollo_compiler::schema::ExtendedType; use arbitrary::Unstructured; +use apollo_compiler::ast::{Definition, InputObjectTypeDefinition, Name, Type}; + pub(crate) trait DefinitionExt { fn ty(&self, u: &mut Unstructured) -> arbitrary::Result; } diff --git a/crates/apollo-smith/src/next/ast/directive_definition.rs b/crates/apollo-smith/src/next/ast/directive_definition.rs index ce4d1431f..8f8515601 100644 --- a/crates/apollo-smith/src/next/ast/directive_definition.rs +++ b/crates/apollo-smith/src/next/ast/directive_definition.rs @@ -1,10 +1,11 @@ -use super::document::DocumentExt; -use crate::next::unstructured::Unstructured; +use std::ops::Deref; + use apollo_compiler::ast::{ - Argument, Directive, DirectiveDefinition, DirectiveList, DirectiveLocation, Document, + Argument, Directive, DirectiveDefinition, DirectiveList, DirectiveLocation, }; use apollo_compiler::{Node, Schema}; -use std::ops::Deref; + +use crate::next::unstructured::Unstructured; pub(crate) struct LocationFilter(I, DirectiveLocation); @@ -57,7 +58,7 @@ impl DirectiveDefinitionIterExt for I { if arg.is_required() || u.arbitrary()? { arguments.push(Node::new(Argument { name: arg.name.clone(), - value: Node::new(u.arbitrary_value(arg.ty.deref(), schema)?), + value: Node::new(u.arbitrary_value(schema, arg.ty.deref())?), })) } } diff --git a/crates/apollo-smith/src/next/ast/document.rs b/crates/apollo-smith/src/next/ast/document.rs index dd8634b1b..3d0f80de7 100644 --- a/crates/apollo-smith/src/next/ast/document.rs +++ b/crates/apollo-smith/src/next/ast/document.rs @@ -1,5 +1,6 @@ use paste::paste; +use crate::next::ast::definition::DefinitionKind; use apollo_compiler::ast::{ Definition, DirectiveDefinition, Document, EnumTypeDefinition, EnumTypeExtension, FragmentDefinition, InputObjectTypeDefinition, InputObjectTypeExtension, @@ -18,7 +19,7 @@ macro_rules! access { fn []( &self, u: &mut Unstructured, - ) -> arbitrary::Result<&Node<$ty>> { + ) -> arbitrary::Result>> { let mut existing = self .target() .definitions @@ -31,20 +32,18 @@ macro_rules! access { } }) .collect::>(); - let idx = u.choose_index(existing.len()).map_err(|e|{ - if let arbitrary::Error::EmptyChoose = e { - panic!("no existing definitions of type {}", stringify!($ty)) - } else { - e - } - })?; - Ok(existing.remove(idx)) + match u.choose_index(existing.len()) { + Ok(idx)=> Ok(Some(existing.remove(idx))), + Err(arbitrary::Error::EmptyChoose)=> Ok(None), + Err(e)=> Err(e) + } + } fn []( &mut self, u: &mut Unstructured, - ) -> arbitrary::Result<&mut Node<$ty>> { + ) -> arbitrary::Result>> { let mut existing = self .target_mut() .definitions @@ -57,14 +56,12 @@ macro_rules! access { } }) .collect::>(); - let idx = u.choose_index(existing.len()).map_err(|e|{ - if let arbitrary::Error::EmptyChoose = e { - panic!("no existing definitions of type {}", stringify!($ty)) - } else { - e - } - })?; - Ok(existing.remove(idx)) + + match u.choose_index(existing.len()) { + Ok(idx)=> Ok(Some(existing.remove(idx))), + Err(arbitrary::Error::EmptyChoose)=> Ok(None), + Err(e)=> Err(e) + } } fn []( @@ -110,6 +107,42 @@ pub(crate) trait DocumentExt { access!(EnumTypeExtension); access!(InputObjectTypeExtension); + fn random_definition( + &self, + u: &mut Unstructured, + definitions: Vec, + ) -> arbitrary::Result> { + let mut existing = self + .target() + .definitions + .iter() + .filter(|d| definitions.iter().any(|t| t.matches(*d))) + .collect::>(); + match u.choose_index(existing.len()) { + Ok(idx) => Ok(Some(existing.remove(idx))), + Err(arbitrary::Error::EmptyChoose) => Ok(None), + Err(e) => Err(e), + } + } + + fn random_definition_mut( + &mut self, + u: &mut Unstructured, + definitions: Vec, + ) -> arbitrary::Result> { + let mut existing = self + .target_mut() + .definitions + .iter_mut() + .filter(|d| definitions.iter().any(|t| t.matches(*d))) + .collect::>(); + match u.choose_index(existing.len()) { + Ok(idx) => Ok(Some(existing.remove(idx))), + Err(arbitrary::Error::EmptyChoose) => Ok(None), + Err(e) => Err(e), + } + } + fn target(&self) -> &Document; fn target_mut(&mut self) -> &mut Document; } diff --git a/crates/apollo-smith/src/next/ast/mod.rs b/crates/apollo-smith/src/next/ast/mod.rs index 39d40f880..47671e0bf 100644 --- a/crates/apollo-smith/src/next/ast/mod.rs +++ b/crates/apollo-smith/src/next/ast/mod.rs @@ -1,4 +1,95 @@ +use apollo_compiler::ast::{FieldDefinition, InterfaceTypeDefinition, ObjectTypeDefinition}; +use apollo_compiler::Node; + pub(crate) mod definition; pub(crate) mod directive_definition; pub(crate) mod document; -pub(crate) mod object_type_definition; + +/// macro for accessing fields on ast elements +macro_rules! field_access { + ($ty:ty) => { + paste::paste! { + pub(crate) trait [<$ty Ext>] { + fn random_field( + &self, + u: &mut crate::next::Unstructured, + ) -> arbitrary::Result<&apollo_compiler::Node> { + Ok(u.choose(&self.target().fields).map_err(|e| { + if let arbitrary::Error::EmptyChoose = e { + panic!("no existing fields") + } else { + e + } + })?) + } + + fn random_field_mut( + &mut self, + u: &mut crate::next::Unstructured, + ) -> arbitrary::Result<&mut apollo_compiler::Node> { + let idx = u.choose_index(self.target().fields.len()).map_err(|e| { + if let arbitrary::Error::EmptyChoose = e { + panic!("no existing fields") + } else { + e + } + })?; + Ok(&mut self.target_mut().fields[idx]) + } + + fn sample_fields( + &self, + u: &mut crate::next::Unstructured, + ) -> arbitrary::Result>> { + let existing = self + .target() + .fields + .iter() + .filter(|_| u.arbitrary().unwrap_or(false)) + .collect::>(); + + Ok(existing) + } + fn target(&self) -> &$ty; + fn target_mut(&mut self) -> &mut $ty; + } + + impl [<$ty Ext>] for $ty { + fn target(&self) -> &$ty { + self + } + fn target_mut(&mut self) -> &mut $ty { + self + } + } + } + }; +} + +field_access!(ObjectTypeDefinition); +field_access!(InterfaceTypeDefinition); + +pub(crate) trait HasFields { + fn fields(&self) -> &Vec>; + fn fields_mut(&mut self) -> &mut Vec>; +} + +impl HasFields for ObjectTypeDefinition { + fn fields(&self) -> &Vec> { + &self.fields + } + + fn fields_mut(&mut self) -> &mut Vec> { + &mut self.fields + } +} + +impl HasFields for InterfaceTypeDefinition { + fn fields(&self) -> &Vec> { + &self.fields + } + + fn fields_mut(&mut self) -> &mut Vec> { + &mut self.fields + } +} diff --git a/crates/apollo-smith/src/next/ast/object_type_definition.rs b/crates/apollo-smith/src/next/ast/object_type_definition.rs deleted file mode 100644 index 25053eb8c..000000000 --- a/crates/apollo-smith/src/next/ast/object_type_definition.rs +++ /dev/null @@ -1,20 +0,0 @@ -use arbitrary::Unstructured; - -use apollo_compiler::ast::ObjectTypeDefinition; -use apollo_compiler::Node; - -pub(crate) trait ObjectTypeDefinitionExt { - field_access!(); - - fn target(&self) -> &ObjectTypeDefinition; - fn target_mut(&mut self) -> &mut ObjectTypeDefinition; -} - -impl ObjectTypeDefinitionExt for ObjectTypeDefinition { - fn target(&self) -> &ObjectTypeDefinition { - self - } - fn target_mut(&mut self) -> &mut ObjectTypeDefinition { - self - } -} diff --git a/crates/apollo-smith/src/next/mod.rs b/crates/apollo-smith/src/next/mod.rs index 2e8979917..dd3d6b23a 100644 --- a/crates/apollo-smith/src/next/mod.rs +++ b/crates/apollo-smith/src/next/mod.rs @@ -1,56 +1,11 @@ -use std::any::type_name; use std::path::PathBuf; -use crate::next::unstructured::Unstructured; use apollo_compiler::ast::Document; -use apollo_compiler::validation::WithErrors; -use apollo_compiler::Schema; - -/// macro for accessing fields -macro_rules! field_access { - () => { - fn random_field( - &self, - u: &mut Unstructured, - ) -> arbitrary::Result<&Node> { - Ok(u.choose(&self.target().fields).map_err(|e| { - if let arbitrary::Error::EmptyChoose = e { - panic!("no existing fields") - } else { - e - } - })?) - } +use apollo_compiler::validation::{Valid, WithErrors}; +use apollo_compiler::{ExecutableDocument, Schema}; - fn random_field_mut( - &mut self, - u: &mut Unstructured, - ) -> arbitrary::Result<&mut Node> { - let idx = u.choose_index(self.target().fields.len()).map_err(|e| { - if let arbitrary::Error::EmptyChoose = e { - panic!("no existing fields") - } else { - e - } - })?; - Ok(&mut self.target_mut().fields[idx]) - } +pub use crate::next::unstructured::Unstructured; - fn sample_fields( - &self, - u: &mut Unstructured, - ) -> arbitrary::Result>> { - let existing = self - .target() - .fields - .iter() - .filter(|_| u.arbitrary().unwrap_or(false)) - .collect::>(); - - Ok(existing) - } - }; -} mod ast; mod mutations; mod schema; @@ -61,80 +16,163 @@ pub enum Error { #[error("arbitrary error")] Arbitrary(#[from] arbitrary::Error), - #[error("schema validation")] - Validation(WithErrors), + #[error("schema document validation failed")] + SchemaDocumentValidation { + doc: Document, + errors: WithErrors, + }, + + #[error("executable document validation failed")] + ExecutableDocumentValidation { + doc: Document, + schema: Valid, + errors: WithErrors, + }, - #[error("schema validation passed, but should have failed")] - ExpectedValidationFail(Document), + #[error("validation passed, but should have failed")] + ExpectedValidationFail { doc: Document, mutation: String }, + + #[error("the serialized AST did not round trip to an identical AST")] + SerializationInconsistency { original: Document, new: Document }, #[error("parse error")] Parse(WithErrors), + + #[error("reparse error")] + Reparse { + doc: Document, + errors: WithErrors, + }, } -pub fn generate_schema_document(input: &[u8]) -> Result { - let mut u = Unstructured::new(input); +pub fn generate_schema_document(u: &mut Unstructured) -> Result { let mut doc = Document::parse( "type Query { me: String }".to_string(), PathBuf::from("synthetic"), ) .map_err(Error::Parse)?; // Start with a minimal schema - let mutations = mutations::all_mutations(); - for mutation in &mutations { - println!("mutation {}", mutation.type_name()); - } + let mutations = mutations::schema_mutations(); let mut schema = doc.to_schema().expect("initial schema must be valid"); - for n in 0..1000 { - println!("iteration: {}", n); - let mut mutation = u.choose(&mutations)?; - println!("applying mutation: {} ", mutation.type_name()); + for _ in 0..1000 { + if u.len() == 0 { + // We ran out of data abort. This is not an error + return Err(Error::Arbitrary(arbitrary::Error::NotEnoughData))?; + } + + let mutation = u.choose(&mutations)?; + let mut new_doc = doc.clone(); // First let's modify the document. We use the schema because it has all the built-in definitions in it. - mutation.apply(&mut u, &mut doc, &schema)?; + if !mutation.apply(u, &mut new_doc, &schema)? { + // The mutation didn't apply, let's try another one + continue; + } - // Let's reparse the document to check that it can be parsed - Document::parse(doc.to_string(), PathBuf::from("synthetic")).map_err(Error::Parse)?; // Now let's validate that the schema says it's OK - - match (mutation.is_valid(), doc.to_schema_validate()) { + match (mutation.is_valid(), new_doc.to_schema_validate()) { (true, Ok(new_schema)) => { + // Let's reparse the document to check that it can be parsed + let reparsed = Document::parse(new_doc.to_string(), PathBuf::from("synthetic")) + .map_err(|e| Error::Reparse { + doc: new_doc.clone(), + errors: e, + })?; + + // The reparsed document should be the same as the original document + if reparsed != new_doc { + return Err(Error::SerializationInconsistency { + original: new_doc, + new: reparsed, + }); + } + + // Let's try and create an executable document from the schema + generate_executable_document(u, &new_schema)?; + schema = new_schema.into_inner(); + doc = new_doc; continue; } (true, Err(e)) => { - return Err(Error::Validation(e)); + return Err(Error::SchemaDocumentValidation { + doc: new_doc, + errors: e, + }); } (false, Ok(_)) => { - return Err(Error::ExpectedValidationFail(doc)); + return Err(Error::ExpectedValidationFail { + doc: new_doc, + mutation: mutation.type_name().to_string(), + }); } (false, Err(_)) => { - // Validation was expected to fail, we can't continue as we mutated the doc - return Ok(doc); + // Validation was expected to fail, we can continue using the old doc and schema + continue; } } } - Ok(doc) } -#[cfg(test)] -mod test { - use crate::next::{generate_schema_document, Error}; - use apollo_compiler::ast::Document; - use apollo_compiler::Schema; +pub(crate) fn generate_executable_document( + u: &mut Unstructured, + schema: &Valid, +) -> Result { + let mut doc = Document::new(); + let mutations = mutations::executable_document_mutations(); + for _ in 0..1000 { + if u.len() == 0 { + // We ran out of data abort. This is not an error + return Err(Error::Arbitrary(arbitrary::Error::NotEnoughData))?; + } + let mutation = u.choose(&mutations)?; + let mut new_doc = doc.clone(); + // First let's modify the document. + if !mutation.apply(u, &mut new_doc, &schema)? { + // The mutation didn't apply, let's try another one + continue; + } - #[test] - fn test_schema() { - let f = Schema::builder().add_ast(&Document::new()).build().unwrap(); - println!("{:?}", f.types.len()); - } + // Now let's validate that the schema says it's OK - #[test] - fn test() { - let input = b"293ur928jff029jf0293ff20983nr0v243ucm8unr4pv938uavtnpb98aun34vtr98aun23vr892auna98r3unv298ua43arp9vu32a3p538umq2v4098cutrnfavwynr9ha28pbz9vuwrA29P38AU[R09UMA2[0893U5NC9A8P3NRV"; - match generate_schema_document(input) { - Ok(_) => {} - Err(e) => { - panic!("error: {:?}", e) + match (mutation.is_valid(), new_doc.to_executable_validate(schema)) { + (true, Ok(_)) => { + // Let's reparse the document to check that it can be parsed + let reparsed = Document::parse(new_doc.to_string(), PathBuf::from("synthetic")) + .map_err(|e| Error::Reparse { + doc: new_doc.clone(), + errors: e, + })?; + + // The reparsed document should be the same as the original document + if reparsed != new_doc { + return Err(Error::SerializationInconsistency { + original: new_doc, + new: reparsed, + }); + } + + doc = new_doc; + continue; + } + (true, Err(e)) => { + return Err(Error::ExecutableDocumentValidation { + doc: new_doc, + schema: schema.clone(), + errors: e, + }); + } + (false, Ok(_)) => { + return Err(Error::ExpectedValidationFail { + doc: new_doc, + mutation: mutation.type_name().to_string(), + }); + } + (false, Err(_)) => { + // Validation was expected to fail, we can continue using the old doc and schema + continue; } } } + + Ok(doc) } diff --git a/crates/apollo-smith/src/next/mutations/add_directive_definition.rs b/crates/apollo-smith/src/next/mutations/add_directive_definition.rs index d8f6a3f0b..916e5b3a8 100644 --- a/crates/apollo-smith/src/next/mutations/add_directive_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_directive_definition.rs @@ -1,7 +1,6 @@ use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; -use crate::next::ast::document::DocumentExt; use crate::next::mutations::Mutation; use crate::next::unstructured::Unstructured; @@ -12,13 +11,13 @@ impl Mutation for AddDirectiveDefinition { u: &mut Unstructured, doc: &mut Document, schema: &Schema, - ) -> arbitrary::Result<()> { + ) -> arbitrary::Result { doc.definitions .push(Definition::DirectiveDefinition(Node::new( u.arbitrary_directive_definition(schema)?, ))); - Ok(()) + Ok(true) } fn is_valid(&self) -> bool { true diff --git a/crates/apollo-smith/src/next/mutations/add_enum_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_enum_type_definition.rs index 8dd16676a..dc9fb4b0d 100644 --- a/crates/apollo-smith/src/next/mutations/add_enum_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_enum_type_definition.rs @@ -1,7 +1,6 @@ use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; -use crate::next::ast::document::DocumentExt; use crate::next::mutations::Mutation; use crate::next::unstructured::Unstructured; @@ -12,13 +11,13 @@ impl Mutation for AddEnumTypeDefinition { u: &mut Unstructured, doc: &mut Document, schema: &Schema, - ) -> arbitrary::Result<()> { + ) -> arbitrary::Result { doc.definitions .push(Definition::EnumTypeDefinition(Node::new( u.arbitrary_enum_type_definition(schema)?, ))); - Ok(()) + Ok(true) } fn is_valid(&self) -> bool { true diff --git a/crates/apollo-smith/src/next/mutations/add_input_object_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_input_object_type_definition.rs index 646588e5a..d269a6456 100644 --- a/crates/apollo-smith/src/next/mutations/add_input_object_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_input_object_type_definition.rs @@ -1,7 +1,6 @@ use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; -use crate::next::ast::document::DocumentExt; use crate::next::mutations::Mutation; use crate::next::unstructured::Unstructured; @@ -12,12 +11,12 @@ impl Mutation for AddInputObjectTypeDefinition { u: &mut Unstructured, doc: &mut Document, schema: &Schema, - ) -> arbitrary::Result<()> { + ) -> arbitrary::Result { doc.definitions .push(Definition::InputObjectTypeDefinition(Node::new( u.arbitrary_input_object_type_definition(schema)?, ))); - Ok(()) + Ok(true) } fn is_valid(&self) -> bool { diff --git a/crates/apollo-smith/src/next/mutations/add_interface_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_interface_type_definition.rs index 1cca3e287..4fc7b27fa 100644 --- a/crates/apollo-smith/src/next/mutations/add_interface_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_interface_type_definition.rs @@ -1,7 +1,6 @@ use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; -use crate::next::ast::document::DocumentExt; use crate::next::mutations::Mutation; use crate::next::unstructured::Unstructured; @@ -12,13 +11,13 @@ impl Mutation for AddInterfaceTypeDefinition { u: &mut Unstructured, doc: &mut Document, schema: &Schema, - ) -> arbitrary::Result<()> { + ) -> arbitrary::Result { doc.definitions .push(Definition::InterfaceTypeDefinition(Node::new( u.arbitrary_interface_type_definition(schema)?, ))); - Ok(()) + Ok(true) } fn is_valid(&self) -> bool { true diff --git a/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs index 088c58a11..c9e2027b6 100644 --- a/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs @@ -1,7 +1,6 @@ use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; -use crate::next::ast::document::DocumentExt; use crate::next::mutations::Mutation; use crate::next::unstructured::Unstructured; @@ -12,12 +11,12 @@ impl Mutation for AddObjectTypeDefinition { u: &mut Unstructured, doc: &mut Document, schema: &Schema, - ) -> arbitrary::Result<()> { + ) -> arbitrary::Result { doc.definitions .push(Definition::ObjectTypeDefinition(Node::new( u.arbitrary_object_type_definition(schema)?, ))); - Ok(()) + Ok(true) } fn is_valid(&self) -> bool { diff --git a/crates/apollo-smith/src/next/mutations/add_operation_definition.rs b/crates/apollo-smith/src/next/mutations/add_operation_definition.rs new file mode 100644 index 000000000..3bfb6feec --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/add_operation_definition.rs @@ -0,0 +1,25 @@ +use crate::next::mutations::Mutation; + +use crate::next::unstructured::Unstructured; +use apollo_compiler::ast::{Definition, Document}; +use apollo_compiler::{Node, Schema}; + +pub(crate) struct AddOperationDefinition; + +impl Mutation for AddOperationDefinition { + fn apply( + &self, + u: &mut Unstructured, + doc: &mut Document, + schema: &Schema, + ) -> arbitrary::Result { + doc.definitions + .push(Definition::OperationDefinition(Node::new( + u.arbitrary_operation_definition(schema)?, + ))); + Ok(false) + } + fn is_valid(&self) -> bool { + false + } +} diff --git a/crates/apollo-smith/src/next/mutations/add_schema_definition.rs b/crates/apollo-smith/src/next/mutations/add_schema_definition.rs index 2a9e9a405..1055e6e88 100644 --- a/crates/apollo-smith/src/next/mutations/add_schema_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_schema_definition.rs @@ -1,7 +1,6 @@ use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; -use crate::next::ast::document::DocumentExt; use crate::next::mutations::Mutation; use crate::next::unstructured::Unstructured; @@ -12,13 +11,13 @@ impl Mutation for AddSchemaDefiniton { u: &mut Unstructured, doc: &mut Document, schema: &Schema, - ) -> arbitrary::Result<()> { + ) -> arbitrary::Result { // If the document already has a schema definition, we don't need to add another one doc.definitions.push(Definition::SchemaDefinition(Node::new( u.arbitrary_schema_definition(schema)?, ))); - Ok(()) + Ok(true) } fn is_valid(&self) -> bool { true diff --git a/crates/apollo-smith/src/next/mutations/add_union_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_union_type_definition.rs index 8386d3eb1..4d0858d5a 100644 --- a/crates/apollo-smith/src/next/mutations/add_union_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_union_type_definition.rs @@ -1,7 +1,6 @@ use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; -use crate::next::ast::document::DocumentExt; use crate::next::mutations::Mutation; use crate::next::unstructured::Unstructured; @@ -12,13 +11,13 @@ impl Mutation for AddUnionTypeDefinition { u: &mut Unstructured, doc: &mut Document, schema: &Schema, - ) -> arbitrary::Result<()> { + ) -> arbitrary::Result { doc.definitions .push(Definition::UnionTypeDefinition(Node::new( u.arbitrary_union_type_definition(schema)?, ))); - Ok(()) + Ok(true) } fn is_valid(&self) -> bool { true diff --git a/crates/apollo-smith/src/next/mutations/mod.rs b/crates/apollo-smith/src/next/mutations/mod.rs index 4cdc8fe86..67cbb80ec 100644 --- a/crates/apollo-smith/src/next/mutations/mod.rs +++ b/crates/apollo-smith/src/next/mutations/mod.rs @@ -1,14 +1,16 @@ use std::any::type_name; -use crate::next::mutations::add_directive_definition::AddDirectiveDefinition; -use crate::next::mutations::add_input_object_type_definition::AddInputObjectTypeDefinition; -use crate::next::mutations::add_interface_type_definition::AddInterfaceTypeDefinition; use apollo_compiler::ast::Document; use apollo_compiler::Schema; +use crate::next::mutations::add_directive_definition::AddDirectiveDefinition; +use crate::next::mutations::add_input_object_type_definition::AddInputObjectTypeDefinition; +use crate::next::mutations::add_interface_type_definition::AddInterfaceTypeDefinition; use crate::next::mutations::add_object_type_definition::AddObjectTypeDefinition; -use crate::next::mutations::add_schema_definition::AddSchemaDefiniton; +use crate::next::mutations::add_operation_definition::AddOperationDefinition; use crate::next::mutations::add_union_type_definition::AddUnionTypeDefinition; +use crate::next::mutations::remove_all_fields::RemoveAllFields; +use crate::next::mutations::remove_required_field::RemoveRequiredField; use crate::next::unstructured::Unstructured; mod add_directive_definition; @@ -16,17 +18,21 @@ mod add_enum_type_definition; mod add_input_object_type_definition; mod add_interface_type_definition; mod add_object_type_definition; +mod add_operation_definition; mod add_schema_definition; mod add_union_type_definition; mod remove_all_fields; +mod remove_required_field; pub(crate) trait Mutation { + /// Apply the mutation to the document + /// Returns false if the mutation did not apply fn apply( &self, u: &mut Unstructured, doc: &mut Document, schema: &Schema, - ) -> arbitrary::Result<()>; + ) -> arbitrary::Result; fn is_valid(&self) -> bool; fn type_name(&self) -> &str { @@ -34,7 +40,7 @@ pub(crate) trait Mutation { } } -pub(crate) fn all_mutations() -> Vec> { +pub(crate) fn schema_mutations() -> Vec> { vec![ Box::new(AddObjectTypeDefinition), Box::new(AddInterfaceTypeDefinition), @@ -42,5 +48,11 @@ pub(crate) fn all_mutations() -> Vec> { Box::new(AddInputObjectTypeDefinition), Box::new(AddUnionTypeDefinition), Box::new(AddInterfaceTypeDefinition), + Box::new(RemoveAllFields), + Box::new(RemoveRequiredField), ] } + +pub(crate) fn executable_document_mutations() -> Vec> { + vec![Box::new(AddOperationDefinition)] +} diff --git a/crates/apollo-smith/src/next/mutations/remove_all_fields.rs b/crates/apollo-smith/src/next/mutations/remove_all_fields.rs index 0581ec13a..3f4522f7b 100644 --- a/crates/apollo-smith/src/next/mutations/remove_all_fields.rs +++ b/crates/apollo-smith/src/next/mutations/remove_all_fields.rs @@ -1,8 +1,9 @@ use crate::next::mutations::Mutation; +use crate::next::ast::definition::DefinitionKind; use crate::next::ast::document::DocumentExt; use crate::next::unstructured::Unstructured; -use apollo_compiler::ast::Document; +use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::Schema; pub(crate) struct RemoveAllFields; @@ -11,13 +12,26 @@ impl Mutation for RemoveAllFields { &self, u: &mut Unstructured, doc: &mut Document, - schema: &Schema, - ) -> arbitrary::Result<()> { - doc.random_object_type_definition_mut(u)? - .make_mut() - .fields - .clear(); - Ok(()) + _schema: &Schema, + ) -> arbitrary::Result { + match doc.random_definition_mut( + u, + vec![ + DefinitionKind::ObjectTypeDefinition, + DefinitionKind::InterfaceTypeDefinition, + ], + )? { + Some(Definition::ObjectTypeDefinition(definition)) => { + definition.make_mut().fields.clear(); + Ok(true) + } + Some(Definition::InterfaceTypeDefinition(definition)) => { + definition.make_mut().fields.clear(); + Ok(true) + } + + _ => Ok(false), + } } fn is_valid(&self) -> bool { false diff --git a/crates/apollo-smith/src/next/mutations/remove_required_field.rs b/crates/apollo-smith/src/next/mutations/remove_required_field.rs new file mode 100644 index 000000000..e5c7daf71 --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/remove_required_field.rs @@ -0,0 +1,63 @@ +use crate::next::mutations::Mutation; + +use crate::next::ast::definition::DefinitionKind; +use crate::next::ast::document::DocumentExt; +use crate::next::schema::InterfaceTypeExt; +use crate::next::unstructured::Unstructured; +use apollo_compiler::ast::{Definition, Document}; +use apollo_compiler::Schema; + +pub(crate) struct RemoveRequiredField; + +impl Mutation for RemoveRequiredField { + fn apply( + &self, + u: &mut Unstructured, + doc: &mut Document, + schema: &Schema, + ) -> arbitrary::Result { + match doc.random_definition_mut( + u, + vec![ + DefinitionKind::ObjectTypeDefinition, + DefinitionKind::InterfaceTypeDefinition, + ], + )? { + Some(Definition::ObjectTypeDefinition(definition)) => { + if !definition.implements_interfaces.is_empty() { + let interface = schema + .get_interface(u.choose(&definition.implements_interfaces)?) + .expect("interface not found"); + if let Some(field) = interface.random_field(u)? { + definition + .make_mut() + .fields + .retain(|f| f.name != field.name); + return Ok(true); + } + } + Ok(false) + } + Some(Definition::InterfaceTypeDefinition(definition)) => { + if !definition.implements_interfaces.is_empty() { + let interface = schema + .get_interface(u.choose(&definition.implements_interfaces)?) + .expect("interface not found"); + if let Some(field) = interface.random_field(u)? { + definition + .make_mut() + .fields + .retain(|f| f.name != field.name); + return Ok(true); + } + } + Ok(false) + } + + _ => Ok(false), + } + } + fn is_valid(&self) -> bool { + false + } +} diff --git a/crates/apollo-smith/src/next/schema/extended_type.rs b/crates/apollo-smith/src/next/schema/extended_type.rs index 1b371104b..b1d2e456c 100644 --- a/crates/apollo-smith/src/next/schema/extended_type.rs +++ b/crates/apollo-smith/src/next/schema/extended_type.rs @@ -1,7 +1,8 @@ -use apollo_compiler::ast::{Definition, Name, Type}; -use apollo_compiler::schema::ExtendedType; use arbitrary::Unstructured; +use apollo_compiler::ast::{Name, Type}; +use apollo_compiler::schema::ExtendedType; + pub(crate) trait ExtendedTypeExt { fn ty(&self, u: &mut Unstructured) -> arbitrary::Result; } diff --git a/crates/apollo-smith/src/next/schema/mod.rs b/crates/apollo-smith/src/next/schema/mod.rs index 9032d864c..7ce7dfbfc 100644 --- a/crates/apollo-smith/src/next/schema/mod.rs +++ b/crates/apollo-smith/src/next/schema/mod.rs @@ -1,2 +1,91 @@ +use crate::next::ast::HasFields; +use apollo_compiler::schema::{Component, InterfaceType, ObjectType}; + pub(crate) mod extended_type; + +pub(crate) mod object_type; pub(crate) mod schema; + +/// macro for accessing fields on schema elements +macro_rules! field_access { + ($ty:ty) => { + paste::paste! { + pub(crate) trait [<$ty Ext>] { + fn random_field( + &self, + u: &mut crate::next::Unstructured, + ) -> arbitrary::Result>> { + let mut fields = self.target().fields.values().collect::>(); + match u.choose_index(fields.len()) { + Ok(idx)=> Ok(Some(fields.remove(idx))), + Err(arbitrary::Error::EmptyChoose)=> Ok(None), + Err(e)=> Err(e) + } + } + + fn random_field_mut( + &mut self, + u: &mut crate::next::Unstructured, + ) -> arbitrary::Result>> { + let mut fields = self.target_mut().fields.values_mut().collect::>(); + match u.choose_index(fields.len()) { + Ok(idx)=> Ok(Some(fields.remove(idx))), + Err(arbitrary::Error::EmptyChoose)=> Ok(None), + Err(e)=> Err(e) + } + + } + + fn sample_fields( + &self, + u: &mut crate::next::Unstructured, + ) -> arbitrary::Result>> { + let existing = self + .target() + .fields + .values() + .filter(|_| u.arbitrary().unwrap_or(false)) + .collect::>(); + + Ok(existing) + } + fn target(&self) -> &$ty; + fn target_mut(&mut self) -> &mut $ty; + } + + impl [<$ty Ext>] for $ty { + fn target(&self) -> &$ty { + self + } + fn target_mut(&mut self) -> &mut $ty { + self + } + } + } + }; +} + +field_access!(ObjectType); +field_access!(InterfaceType); + +impl HasFields for Component { + fn fields( + &self, + ) -> &std::collections::HashMap< + String, + apollo_compiler::schema::Component, + > { + &self.fields + } +} + +impl HasFields for InterfaceType { + fn fields( + &self, + ) -> &std::collections::HashMap< + String, + apollo_compiler::schema::Component, + > { + &self.fields + } +} diff --git a/crates/apollo-smith/src/next/schema/object_type.rs b/crates/apollo-smith/src/next/schema/object_type.rs new file mode 100644 index 000000000..16412f74f --- /dev/null +++ b/crates/apollo-smith/src/next/schema/object_type.rs @@ -0,0 +1,17 @@ +use apollo_compiler::ast::OperationType; +use apollo_compiler::schema::ObjectType; + +pub(crate) trait ObjectTypeExt { + fn operation_type(&self) -> Option; +} + +impl ObjectTypeExt for ObjectType { + fn operation_type(&self) -> Option { + match self.name.as_str() { + "Query" => Some(OperationType::Query), + "Mutation" => Some(OperationType::Mutation), + "Subscription" => Some(OperationType::Subscription), + _ => None, + } + } +} diff --git a/crates/apollo-smith/src/next/schema/schema.rs b/crates/apollo-smith/src/next/schema/schema.rs index 48b2bcf5a..cf2a6c598 100644 --- a/crates/apollo-smith/src/next/schema/schema.rs +++ b/crates/apollo-smith/src/next/schema/schema.rs @@ -1,14 +1,16 @@ -use crate::next::ast::definition::DefinitionKind; -use crate::next::schema::extended_type::ExtendedTypeKind; -use apollo_compiler::ast::Definition; +use apollo_compiler::ast::OperationType; +use arbitrary::Unstructured; +use paste::paste; + use apollo_compiler::schema::{DirectiveDefinition, ExtendedType}; use apollo_compiler::schema::{ EnumType, InputObjectType, InterfaceType, ObjectType, ScalarType, UnionType, }; use apollo_compiler::Node; use apollo_compiler::Schema; -use arbitrary::Unstructured; -use paste::paste; + +use crate::next::schema::extended_type::ExtendedTypeKind; + macro_rules! access { ($variant: ident, $ty: ty) => { paste! { @@ -121,6 +123,22 @@ pub(crate) trait SchemaExt { Ok(existing) } + fn random_query_mutation_subscription( + &self, + u: &mut Unstructured, + ) -> arbitrary::Result<&Node> { + Ok(*u.choose( + &vec![ + self.target().get_object("Query"), + self.target().get_object("Mutation"), + self.target().get_object("Subscription"), + ] + .into_iter() + .filter_map(|o| o) + .collect::>(), + )?) + } + fn target(&self) -> &Schema; } diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index 3f214db42..9e318232e 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -1,27 +1,36 @@ -use crate::next::ast::directive_definition::DirectiveDefinitionIterExt; -use crate::next::schema::extended_type::{ExtendedTypeExt, ExtendedTypeKind}; -use crate::next::schema::schema::SchemaExt; +use std::ops::{Deref, DerefMut}; + +use arbitrary::Result; + use apollo_compiler::ast::{ - DirectiveDefinition, DirectiveLocation, EnumTypeDefinition, EnumValueDefinition, - FieldDefinition, InputObjectTypeDefinition, InputValueDefinition, InterfaceTypeDefinition, - Name, ObjectTypeDefinition, OperationType, SchemaDefinition, Type, UnionTypeDefinition, Value, + Argument, DirectiveDefinition, DirectiveLocation, EnumTypeDefinition, EnumValueDefinition, + Field, FieldDefinition, FragmentSpread, InlineFragment, InputObjectTypeDefinition, + InputValueDefinition, InterfaceTypeDefinition, Name, ObjectTypeDefinition, OperationDefinition, + OperationType, SchemaDefinition, Selection, Type, UnionTypeDefinition, Value, + VariableDefinition, }; -use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::{ExtendedType, InterfaceType, ObjectType}; use apollo_compiler::{Node, NodeStr, Schema}; -use arbitrary::Result; -use std::ops::{Deref, DerefMut}; -use std::sync::atomic::Ordering; -pub(crate) trait UnstructuredExt { - fn arbitrary_vec Result>( - &mut self, - min: usize, - max: usize, - callback: C, - ) -> Result>; +use crate::next::ast::directive_definition::DirectiveDefinitionIterExt; +use crate::next::ast::HasFields; +use crate::next::schema::extended_type::{ExtendedTypeExt, ExtendedTypeKind}; +use crate::next::schema::object_type::ObjectTypeExt; +use crate::next::schema::schema::SchemaExt; +pub struct Unstructured<'a> { + u: arbitrary::Unstructured<'a>, + counter: usize, } -impl<'a> UnstructuredExt for Unstructured<'a> { - fn arbitrary_vec Result>( + +impl Unstructured<'_> { + pub fn new<'a>(data: &'a [u8]) -> Unstructured<'a> { + Unstructured { + u: arbitrary::Unstructured::new(data), + counter: 0, + } + } + + pub(crate) fn arbitrary_vec Result>( &mut self, min: usize, max: usize, @@ -34,34 +43,16 @@ impl<'a> UnstructuredExt for Unstructured<'a> { } Ok(results) } -} - -pub(crate) trait UnstructuredOption: Sized { - fn optional(self, u: &mut Unstructured) -> Result>; -} - -impl UnstructuredOption for T { - fn optional(self, u: &mut Unstructured) -> Result> { - if u.arbitrary()? { - Ok(Some(self)) + pub(crate) fn arbitrary_optional Result>( + &mut self, + callback: C, + ) -> Result> { + if self.arbitrary()? { + Ok(Some(callback(self)?)) } else { Ok(None) } } -} - -pub(crate) struct Unstructured<'a> { - u: arbitrary::Unstructured<'a>, - counter: usize, -} - -impl Unstructured<'_> { - pub(crate) fn new<'a>(data: &'a [u8]) -> Unstructured<'a> { - Unstructured { - u: arbitrary::Unstructured::new(data), - counter: 0, - } - } pub(crate) fn unique_name(&mut self) -> Name { self.counter = self.counter + 1; @@ -115,7 +106,7 @@ impl Unstructured<'_> { schema: &Schema, ) -> Result { Ok(SchemaDefinition { - description: self.arbitrary_node_str()?.optional(self)?, + description: self.arbitrary_optional(|u| u.arbitrary_node_str())?, directives: schema .sample_directives(self)? .into_iter() @@ -126,16 +117,18 @@ impl Unstructured<'_> { OperationType::Query, schema.random_object_type(self)?.name.clone(), ))), - Node::new(( - OperationType::Mutation, - schema.random_object_type(self)?.name.clone(), - )) - .optional(self)?, - Node::new(( - OperationType::Subscription, - schema.random_object_type(self)?.name.clone(), - )) - .optional(self)?, + self.arbitrary_optional(|u| { + Ok(Node::new(( + OperationType::Mutation, + schema.random_object_type(u)?.name.clone(), + ))) + })?, + self.arbitrary_optional(|u| { + Ok(Node::new(( + OperationType::Subscription, + schema.random_object_type(u)?.name.clone(), + ))) + })?, ] .into_iter() .filter_map(|op| op) @@ -147,8 +140,16 @@ impl Unstructured<'_> { &mut self, schema: &Schema, ) -> Result { + let implements = schema.sample_interface_types(self)?; + let implements_fields = Self::all_fields_from_interfaces(&implements); + let new_fields = self.arbitrary_vec(1, 5, |u| { + Ok(Node::new(u.arbitrary_field_definition( + schema, + DirectiveLocation::InputFieldDefinition, + )?)) + })?; Ok(ObjectTypeDefinition { - description: self.arbitrary_node_str()?.optional(self)?, + description: self.arbitrary_optional(|u| u.arbitrary_node_str())?, name: self.unique_name(), implements_interfaces: schema .sample_interface_types(self)? @@ -160,12 +161,10 @@ impl Unstructured<'_> { .into_iter() .with_location(DirectiveLocation::Object) .try_collect(self, schema)?, - fields: self.arbitrary_vec(1, 5, |u| { - Ok(Node::new(u.arbitrary_field_definition( - schema, - DirectiveLocation::FieldDefinition, - )?)) - })?, + fields: new_fields + .into_iter() + .chain(implements_fields.into_iter()) + .collect(), }) } @@ -174,7 +173,7 @@ impl Unstructured<'_> { schema: &Schema, ) -> Result { Ok(DirectiveDefinition { - description: self.arbitrary_node_str()?.optional(self)?, + description: self.arbitrary_optional(|u| u.arbitrary_node_str())?, name: self.unique_name(), arguments: self.arbitrary_vec(0, 5, |u| { Ok(Node::new(u.arbitrary_input_value_definition(schema)?)) @@ -189,7 +188,7 @@ impl Unstructured<'_> { schema: &Schema, ) -> Result { Ok(InputObjectTypeDefinition { - description: self.arbitrary_node_str()?.optional(self)?, + description: self.arbitrary_optional(|u| u.arbitrary_node_str())?, name: self.unique_name(), directives: schema .sample_directives(self)? @@ -215,12 +214,11 @@ impl Unstructured<'_> { ], )? .ty(self)?; - let default_value = self - .arbitrary_value(&ty, schema)? - .optional(self)? - .map(Node::new); + let default_value = + self.arbitrary_optional(|u| Ok(Node::new(u.arbitrary_value(schema, &ty)?)))?; + Ok(InputValueDefinition { - description: self.arbitrary_node_str()?.optional(self)?, + description: self.arbitrary_optional(|u| u.arbitrary_node_str())?, name: self.unique_name(), ty: Node::new(ty), default_value, @@ -237,7 +235,7 @@ impl Unstructured<'_> { schema: &Schema, ) -> Result { Ok(EnumTypeDefinition { - description: self.arbitrary_node_str()?.optional(self)?, + description: self.arbitrary_optional(|u| u.arbitrary_node_str())?, name: self.unique_name(), directives: schema .sample_directives(self)? @@ -246,7 +244,7 @@ impl Unstructured<'_> { .try_collect(self, schema)?, values: self.arbitrary_vec(0, 5, |u| { Ok(Node::new(EnumValueDefinition { - description: u.arbitrary_node_str()?.optional(u)?, + description: u.arbitrary_optional(|u| u.arbitrary_node_str())?, value: u.unique_name(), directives: schema .sample_directives(u)? @@ -263,7 +261,7 @@ impl Unstructured<'_> { schema: &Schema, ) -> Result { Ok(UnionTypeDefinition { - description: self.arbitrary_node_str()?.optional(self)?, + description: self.arbitrary_optional(|u| u.arbitrary_node_str())?, name: self.unique_name(), directives: schema .sample_directives(self)? @@ -282,11 +280,20 @@ impl Unstructured<'_> { &mut self, schema: &Schema, ) -> Result { + // All interfaces need to have all the fields from the interfaces they implement. + let implements = schema.sample_interface_types(self)?; + let implements_fields = Self::all_fields_from_interfaces(&implements); + let new_fields = self.arbitrary_vec(1, 5, |u| { + Ok(Node::new(u.arbitrary_field_definition( + schema, + DirectiveLocation::InputFieldDefinition, + )?)) + })?; + Ok(InterfaceTypeDefinition { - description: self.arbitrary_node_str()?.optional(self)?, + description: self.arbitrary_optional(|u| u.arbitrary_node_str())?, name: self.unique_name(), - implements_interfaces: schema - .sample_interface_types(self)? + implements_interfaces: implements .iter() .map(|interface| interface.name.clone()) .collect(), @@ -295,22 +302,31 @@ impl Unstructured<'_> { .into_iter() .with_location(DirectiveLocation::Interface) .try_collect(self, schema)?, - fields: self.arbitrary_vec(0, 5, |u| { - Ok(Node::new(u.arbitrary_field_definition( - schema, - DirectiveLocation::InputFieldDefinition, - )?)) - })?, + fields: new_fields + .into_iter() + .chain(implements_fields.into_iter()) + .collect(), }) } + fn all_fields_from_interfaces( + implements: &Vec<&Node>, + ) -> Vec> { + let implements_fields = implements + .iter() + .flat_map(|interface| interface.fields.values()) + .map(|field| field.deref().clone()) + .collect::>(); + implements_fields + } + pub(crate) fn arbitrary_field_definition( &mut self, schema: &Schema, directive_location: DirectiveLocation, ) -> Result { Ok(FieldDefinition { - description: self.arbitrary_node_str()?.optional(self)?, + description: self.arbitrary_optional(|u| u.arbitrary_node_str())?, name: self.unique_name(), arguments: self.arbitrary_vec(0, 5, |u| { Ok(Node::new(u.arbitrary_input_value_definition(schema)?)) @@ -335,11 +351,11 @@ impl Unstructured<'_> { }) } - pub(crate) fn arbitrary_value(&mut self, ty: &Type, schema: &Schema) -> Result { + pub(crate) fn arbitrary_value(&mut self, schema: &Schema, ty: &Type) -> Result { match ty { Type::Named(ty) => { if self.arbitrary()? { - self.arbitrary_value(&Type::NonNullNamed(ty.clone()), schema) + self.arbitrary_value(schema, &Type::NonNullNamed(ty.clone())) } else { Ok(Value::Null) } @@ -347,7 +363,7 @@ impl Unstructured<'_> { Type::List(ty) => { if self.arbitrary()? { Ok(Value::List(self.arbitrary_vec(0, 5, |u| { - Ok(Node::new(u.arbitrary_value(ty, schema)?)) + Ok(Node::new(u.arbitrary_value(schema, ty)?)) })?)) } else { Ok(Value::Null) @@ -356,9 +372,15 @@ impl Unstructured<'_> { Type::NonNullNamed(ty) => match schema.types.get(ty).expect("type must exist") { ExtendedType::Scalar(ty) => { if ty.name == "Int" { - Ok(Value::from(self.arbitrary::()?)) - } else if ty.name == "Float" { Ok(Value::from(self.arbitrary::()?)) + } else if ty.name == "Float" { + loop { + // not ideal, but graphql requires finite values is not a valid value. + let val = self.arbitrary::()?; + if val.is_finite() { + return Ok(Value::from(val)); + } + } } else if ty.name == "Boolean" { Ok(Value::Boolean(self.arbitrary()?)) } else { @@ -370,7 +392,7 @@ impl Unstructured<'_> { for (name, definition) in &ty.fields { values.push(( name.clone(), - Node::new(self.arbitrary_value(&definition.ty, schema)?), + Node::new(self.arbitrary_value(schema, &definition.ty)?), )); } Ok(Value::Object(values)) @@ -379,7 +401,7 @@ impl Unstructured<'_> { let values = ty .values .iter() - .map(|(name, v)| &v.value) + .map(|(_name, v)| &v.value) .collect::>(); if values.is_empty() { panic!("enum must have at least one value") @@ -392,7 +414,7 @@ impl Unstructured<'_> { for (name, definition) in &ty.fields { values.push(( name.clone(), - Node::new(self.arbitrary_value(&definition.ty, schema)?), + Node::new(self.arbitrary_value(schema, &definition.ty)?), )); } Ok(Value::Object(values)) @@ -403,11 +425,99 @@ impl Unstructured<'_> { }, Type::NonNullList(ty) => { Ok(Value::List(self.arbitrary_vec(0, 5, |u| { - Ok(Node::new(u.arbitrary_value(ty, schema)?)) + Ok(Node::new(u.arbitrary_value(schema, ty)?)) })?)) } } } + + pub(crate) fn arbitrary_operation_definition( + &mut self, + schema: &Schema, + ) -> Result { + let operation = schema.random_query_mutation_subscription(self)?; + + Ok(OperationDefinition { + operation_type: operation + .deref() + .operation_type() + .expect("top level operation must have type"), + name: self.arbitrary_optional(|u| Ok(u.unique_name()))?, + variables: vec![], + directives: schema + .sample_directives(self)? + .into_iter() + .with_location(DirectiveLocation::Field) + .try_collect(self, schema)?, + selection_set: self.arbitrary_vec(0, 5, |u| { + Ok(u.arbitrary_selection(schema, operation.deref())?) + })?, + }) + } + + fn arbitrary_variable_definition(&mut self, schema: &Schema) -> Result { + let ty = schema + .random_type(self, vec![ExtendedTypeKind::InputObjectTypeDefinition])? + .ty(self)?; + Ok(VariableDefinition { + name: self.unique_name(), + ty: Node::new(ty), + default_value: self + .arbitrary_optional(|u| Ok(Node::new(u.arbitrary_value(&ty, schema)?)))?, + directives: schema + .sample_directives(self)? + .into_iter() + .with_location(DirectiveLocation::ArgumentDefinition) + .try_collect(self, schema)?, + }) + } + + fn arbitrary_inline_fragment(&mut self, schema: &Schema) -> Result { + let ty = schema.random_type( + self, + vec![ExtendedTypeKind::Object, ExtendedTypeKind::Interface], + )?; + + Ok(InlineFragment { + type_condition: self.arbitrary_optional(|u| Ok(ty.name().clone()))?, + directives: schema + .sample_directives(self)? + .into_iter() + .with_location(DirectiveLocation::InlineFragment) + .try_collect(self, schema)?, + selection_set: self.arbitrary_vec(0, 5, |u| Ok(u.arbitrary_selection(schema, ty)?))?, + }) + } + + fn arbitrary_fragment_spread(&mut self, schema: &Schema) -> Result { + Ok(FragmentSpread { + fragment_name: self.unique_name(), + directives: schema + .sample_directives(self)? + .into_iter() + .with_location(DirectiveLocation::FragmentSpread) + .try_collect(self, schema)?, + }) + } + + fn arbitrary_selection( + &mut self, + schema: &Schema, + object_type: &dyn HasFields, + ) -> Result { + if let Some(field) = object_type.random_field(self)? { + match self.choose_index(3) { + Ok(0) => Ok(Selection::Field(Node::new(self.arbitrary_field(schema)?))), + Ok(1) => Ok(Selection::FragmentSpread(Node::new( + self.arbitrary_fragment_spread(schema)?, + ))), + Ok(2) => Ok(Selection::InlineFragment(Node::new( + self.arbitrary_inline_fragment(schema)?, + ))), + _ => unreachable!(), + } + } + } } impl<'a> Deref for Unstructured<'a> { diff --git a/fuzz/fuzz_targets/parser_next.rs b/fuzz/fuzz_targets/parser_next.rs index 2a7c5fc76..d164adb05 100644 --- a/fuzz/fuzz_targets/parser_next.rs +++ b/fuzz/fuzz_targets/parser_next.rs @@ -1,15 +1,66 @@ #![no_main] -use apollo_parser::Parser; -use apollo_rs_fuzz::{generate_valid_document, log_gql_doc}; -use libfuzzer_sys::arbitrary::Unstructured; -use libfuzzer_sys::fuzz_target; -use log::debug; + use std::panic; +use libfuzzer_sys::{arbitrary, fuzz_target}; + +use apollo_smith::next::Error; + fuzz_target!(|data: &[u8]| { let _ = env_logger::try_init(); if let Err(e) = apollo_rs_fuzz::generate_schema_document(data) { - log::info!("error: {:?}", e); + match &e { + Error::Arbitrary(arbitrary::Error::NotEnoughData) => { + return; + } + Error::Arbitrary(e) => { + println!("arbitrary error: {}", e); + } + Error::Parse(doc) => { + println!("{}\ndoc:\n{}\nerrors:\n{}", e, doc.to_string(), doc.errors); + } + Error::ExpectedValidationFail { doc, mutation } => { + println!("{}\nmutation:\n{}\ndoc:\n{}", e, mutation, doc.to_string()); + } + Error::SerializationInconsistency { original, new } => { + println!( + "{}\noriginal:\n{}\nnew:\n{}", + e, + original.to_string(), + new.to_string() + ); + } + Error::SchemaDocumentValidation { doc, errors } => { + println!( + "{}\ndoc:\n{}\nerrors:\n{}", + e, + doc.to_string(), + errors.errors + ); + } + Error::Reparse { doc, errors } => { + println!( + "{}\ndoc:\n{}\nerrors:\n{}", + e, + doc.to_string(), + errors.errors + ); + } + Error::ExecutableDocumentValidation { + doc, + schema, + errors, + } => { + println!( + "{}\nschena\n{}\ndoc:\n{}\nerrors:\n{}", + e, + schema.to_string(), + doc.to_string(), + errors.errors + ); + } + } + panic!("error detected: {}", e); } }); diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs index a02b58c25..32e20db5f 100644 --- a/fuzz/src/lib.rs +++ b/fuzz/src/lib.rs @@ -1,8 +1,8 @@ -use apollo_compiler::Schema; use apollo_smith::DocumentBuilder; + use libfuzzer_sys::arbitrary::{Result, Unstructured}; -/// This generate an arbitrary valid GraphQL document +/// This generates an arbitrary valid GraphQL document pub fn generate_valid_document(input: &[u8]) -> Result { drop(env_logger::try_init()); @@ -18,7 +18,9 @@ pub fn generate_schema_document( ) -> std::result::Result<(), apollo_smith::next::Error> { drop(env_logger::try_init()); - apollo_smith::next::generate_schema_document(input)?; + apollo_smith::next::generate_schema_document(&mut apollo_smith::next::Unstructured::new( + input, + ))?; Ok(()) } From ed33deaa9dcc1e5f3c0c5de1f681b575a5d52af5 Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Thu, 14 Mar 2024 09:08:26 +0000 Subject: [PATCH 10/23] Temp --- crates/apollo-smith/src/next/ast/mod.rs | 29 +++++++++++-- crates/apollo-smith/src/next/schema/mod.rs | 44 +++++++++++++------- crates/apollo-smith/src/next/unstructured.rs | 6 +-- 3 files changed, 56 insertions(+), 23 deletions(-) diff --git a/crates/apollo-smith/src/next/ast/mod.rs b/crates/apollo-smith/src/next/ast/mod.rs index 47671e0bf..1a46b4125 100644 --- a/crates/apollo-smith/src/next/ast/mod.rs +++ b/crates/apollo-smith/src/next/ast/mod.rs @@ -1,4 +1,6 @@ -use apollo_compiler::ast::{FieldDefinition, InterfaceTypeDefinition, ObjectTypeDefinition}; +use apollo_compiler::ast::{ + Definition, FieldDefinition, InterfaceTypeDefinition, ObjectTypeDefinition, +}; use apollo_compiler::Node; pub(crate) mod definition; @@ -69,12 +71,12 @@ macro_rules! field_access { field_access!(ObjectTypeDefinition); field_access!(InterfaceTypeDefinition); -pub(crate) trait HasFields { +pub(crate) trait DefinitionHasFields { fn fields(&self) -> &Vec>; fn fields_mut(&mut self) -> &mut Vec>; } -impl HasFields for ObjectTypeDefinition { +impl DefinitionHasFields for ObjectTypeDefinition { fn fields(&self) -> &Vec> { &self.fields } @@ -84,7 +86,7 @@ impl HasFields for ObjectTypeDefinition { } } -impl HasFields for InterfaceTypeDefinition { +impl DefinitionHasFields for InterfaceTypeDefinition { fn fields(&self) -> &Vec> { &self.fields } @@ -93,3 +95,22 @@ impl HasFields for InterfaceTypeDefinition { &mut self.fields } } + +impl DefinitionHasFields for Definition { + fn fields(&self) -> &Vec> { + static EMPTY: Vec> = Vec::new(); + match self { + Definition::ObjectTypeDefinition(d) => &d.fields, + Definition::InterfaceTypeDefinition(d) => &d.fields, + _ => &EMPTY, + } + } + + fn fields_mut(&mut self) -> &mut Vec> { + match self { + Definition::ObjectTypeDefinition(d) => &mut d.fields, + Definition::InterfaceTypeDefinition(d) => &mut d.fields, + _ => panic!("fields_mut cannot be called on a definition that has no fields"), + } + } +} diff --git a/crates/apollo-smith/src/next/schema/mod.rs b/crates/apollo-smith/src/next/schema/mod.rs index 7ce7dfbfc..7686f8a7f 100644 --- a/crates/apollo-smith/src/next/schema/mod.rs +++ b/crates/apollo-smith/src/next/schema/mod.rs @@ -1,5 +1,7 @@ -use crate::next::ast::HasFields; -use apollo_compiler::schema::{Component, InterfaceType, ObjectType}; +use apollo_compiler::ast::{FieldDefinition, Name}; +use apollo_compiler::schema::{Component, ExtendedType, InterfaceType, ObjectType}; +use apollo_compiler::Node; +use indexmap::IndexMap; pub(crate) mod extended_type; @@ -68,24 +70,34 @@ macro_rules! field_access { field_access!(ObjectType); field_access!(InterfaceType); -impl HasFields for Component { - fn fields( - &self, - ) -> &std::collections::HashMap< - String, - apollo_compiler::schema::Component, - > { +pub(crate) trait TypeHasFields { + fn fields(&self) -> &IndexMap>; + fn random_field(&self, u:) -> Option<&Node> { + let mut fields = self.fields().values().collect::>(); + fields.c() + } + +} + +impl TypeHasFields for ObjectType { + fn fields(&self) -> &IndexMap> { &self.fields } } -impl HasFields for InterfaceType { - fn fields( - &self, - ) -> &std::collections::HashMap< - String, - apollo_compiler::schema::Component, - > { +impl TypeHasFields for InterfaceType { + fn fields(&self) -> &IndexMap> { &self.fields } } + +impl TypeHasFields for ExtendedType { + fn fields(&self) -> &IndexMap> { + static EMPTY: IndexMap> = IndexMap::new(); + match self { + ExtendedType::Object(t) => t.fields(), + ExtendedType::Interface(t) => t.fields(), + _ => &EMPTY, + } + } +} diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index 9e318232e..d050a9c53 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -13,7 +13,7 @@ use apollo_compiler::schema::{ExtendedType, InterfaceType, ObjectType}; use apollo_compiler::{Node, NodeStr, Schema}; use crate::next::ast::directive_definition::DirectiveDefinitionIterExt; -use crate::next::ast::HasFields; +use crate::next::ast::DefinitionHasFields; use crate::next::schema::extended_type::{ExtendedTypeExt, ExtendedTypeKind}; use crate::next::schema::object_type::ObjectTypeExt; use crate::next::schema::schema::SchemaExt; @@ -463,7 +463,7 @@ impl Unstructured<'_> { name: self.unique_name(), ty: Node::new(ty), default_value: self - .arbitrary_optional(|u| Ok(Node::new(u.arbitrary_value(&ty, schema)?)))?, + .arbitrary_optional(|u| Ok(Node::new(u.arbitrary_value(schema, &ty)?)))?, directives: schema .sample_directives(self)? .into_iter() @@ -503,7 +503,7 @@ impl Unstructured<'_> { fn arbitrary_selection( &mut self, schema: &Schema, - object_type: &dyn HasFields, + object_type: &dyn super::schema::TypeHasFields, ) -> Result { if let Some(field) = object_type.random_field(self)? { match self.choose_index(3) { From 920ff5b78506ae054d11b3aab27179a915b85148 Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Thu, 14 Mar 2024 16:54:35 +0000 Subject: [PATCH 11/23] Make things compile --- crates/apollo-smith/src/next/ast/mod.rs | 8 ++- crates/apollo-smith/src/next/schema/mod.rs | 14 +++-- crates/apollo-smith/src/next/schema/schema.rs | 1 - crates/apollo-smith/src/next/unstructured.rs | 55 +++++++++++++------ 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/crates/apollo-smith/src/next/ast/mod.rs b/crates/apollo-smith/src/next/ast/mod.rs index 1a46b4125..3e1426546 100644 --- a/crates/apollo-smith/src/next/ast/mod.rs +++ b/crates/apollo-smith/src/next/ast/mod.rs @@ -106,11 +106,13 @@ impl DefinitionHasFields for Definition { } } - fn fields_mut(&mut self) -> &mut Vec> { + fn fields_mut<'a>(&mut self) -> &mut Vec> { match self { - Definition::ObjectTypeDefinition(d) => &mut d.fields, - Definition::InterfaceTypeDefinition(d) => &mut d.fields, + Definition::ObjectTypeDefinition(d) => d.make_mut().fields_mut(), + Definition::InterfaceTypeDefinition(d) => d.make_mut().fields_mut(), _ => panic!("fields_mut cannot be called on a definition that has no fields"), } } } + + diff --git a/crates/apollo-smith/src/next/schema/mod.rs b/crates/apollo-smith/src/next/schema/mod.rs index 7686f8a7f..a2d4c02fd 100644 --- a/crates/apollo-smith/src/next/schema/mod.rs +++ b/crates/apollo-smith/src/next/schema/mod.rs @@ -1,7 +1,8 @@ +use std::sync::OnceLock; use apollo_compiler::ast::{FieldDefinition, Name}; use apollo_compiler::schema::{Component, ExtendedType, InterfaceType, ObjectType}; -use apollo_compiler::Node; use indexmap::IndexMap; +use crate::next::Unstructured; pub(crate) mod extended_type; @@ -72,9 +73,10 @@ field_access!(InterfaceType); pub(crate) trait TypeHasFields { fn fields(&self) -> &IndexMap>; - fn random_field(&self, u:) -> Option<&Node> { - let mut fields = self.fields().values().collect::>(); - fields.c() + fn random_field(&self, u: &mut Unstructured) -> arbitrary::Result<&Component> { + // Types always have at least one field + let fields = self.fields().values().collect::>(); + Ok(fields[u.choose_index(fields.len())?]) } } @@ -93,11 +95,11 @@ impl TypeHasFields for InterfaceType { impl TypeHasFields for ExtendedType { fn fields(&self) -> &IndexMap> { - static EMPTY: IndexMap> = IndexMap::new(); + static EMPTY: OnceLock>> = OnceLock::new(); match self { ExtendedType::Object(t) => t.fields(), ExtendedType::Interface(t) => t.fields(), - _ => &EMPTY, + _ => &EMPTY.get_or_init(||Default::default()), } } } diff --git a/crates/apollo-smith/src/next/schema/schema.rs b/crates/apollo-smith/src/next/schema/schema.rs index cf2a6c598..3a6ff45d4 100644 --- a/crates/apollo-smith/src/next/schema/schema.rs +++ b/crates/apollo-smith/src/next/schema/schema.rs @@ -1,4 +1,3 @@ -use apollo_compiler::ast::OperationType; use arbitrary::Unstructured; use paste::paste; diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index d050a9c53..92380bea2 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -9,11 +9,10 @@ use apollo_compiler::ast::{ OperationType, SchemaDefinition, Selection, Type, UnionTypeDefinition, Value, VariableDefinition, }; -use apollo_compiler::schema::{ExtendedType, InterfaceType, ObjectType}; +use apollo_compiler::schema::{ExtendedType, InterfaceType, }; use apollo_compiler::{Node, NodeStr, Schema}; use crate::next::ast::directive_definition::DirectiveDefinitionIterExt; -use crate::next::ast::DefinitionHasFields; use crate::next::schema::extended_type::{ExtendedTypeExt, ExtendedTypeKind}; use crate::next::schema::object_type::ObjectTypeExt; use crate::next::schema::schema::SchemaExt; @@ -461,9 +460,9 @@ impl Unstructured<'_> { .ty(self)?; Ok(VariableDefinition { name: self.unique_name(), - ty: Node::new(ty), default_value: self .arbitrary_optional(|u| Ok(Node::new(u.arbitrary_value(schema, &ty)?)))?, + ty: Node::new(ty), directives: schema .sample_directives(self)? .into_iter() @@ -479,7 +478,7 @@ impl Unstructured<'_> { )?; Ok(InlineFragment { - type_condition: self.arbitrary_optional(|u| Ok(ty.name().clone()))?, + type_condition: self.arbitrary_optional(|_| Ok(ty.name().clone()))?, directives: schema .sample_directives(self)? .into_iter() @@ -503,20 +502,44 @@ impl Unstructured<'_> { fn arbitrary_selection( &mut self, schema: &Schema, - object_type: &dyn super::schema::TypeHasFields, + ty: &dyn super::schema::TypeHasFields, ) -> Result { - if let Some(field) = object_type.random_field(self)? { - match self.choose_index(3) { - Ok(0) => Ok(Selection::Field(Node::new(self.arbitrary_field(schema)?))), - Ok(1) => Ok(Selection::FragmentSpread(Node::new( - self.arbitrary_fragment_spread(schema)?, - ))), - Ok(2) => Ok(Selection::InlineFragment(Node::new( - self.arbitrary_inline_fragment(schema)?, - ))), - _ => unreachable!(), - } + match self.choose_index(3) { + Ok(0) => { + let field = ty.random_field(self)?; + let field_ty = schema.types.get(field.ty.inner_named_type()).expect("type must exist"); + let selection_set = if field_ty.is_object() || field_ty.is_interface() { + self.arbitrary_vec(0, 5, |u| { + Ok(u.arbitrary_selection(schema, field_ty)?) + })? + } else { + vec![] + }; + Ok(Selection::Field(Node::new(Field { + alias: self.arbitrary_optional(|u|Ok(u.unique_name()))?, + name: self.unique_name(), + arguments: self.arbitrary_vec(0, 5, |u| { + Ok(Node::new(Argument { + name: u.unique_name(), + value: Node::new(u.arbitrary_value(schema, &field.ty)?), + })) + })?, + directives: schema.sample_directives(self)? + .into_iter() + .with_location(DirectiveLocation::Field) + .try_collect(self, schema)?, + selection_set, + }))) + }, + Ok(1) => Ok(Selection::FragmentSpread(Node::new( + self.arbitrary_fragment_spread(schema)?, + ))), + Ok(2) => Ok(Selection::InlineFragment(Node::new( + self.arbitrary_inline_fragment(schema)?, + ))), + _ => unreachable!(), } + } } From 73b664ca0c03e19a94be1214cae8d3134b9ee60d Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Thu, 14 Mar 2024 18:07:08 +0000 Subject: [PATCH 12/23] temp --- crates/apollo-smith/src/next/README.md | 2 ++ crates/apollo-smith/src/next/mod.rs | 32 +++++++++++++------ .../mutations/add_operation_definition.rs | 4 +-- crates/apollo-smith/src/next/mutations/mod.rs | 2 +- crates/apollo-smith/src/next/unstructured.rs | 20 +++++++----- fuzz/fuzz_targets/parser_next.rs | 25 ++++++++++++--- 6 files changed, 61 insertions(+), 24 deletions(-) create mode 100644 crates/apollo-smith/src/next/README.md diff --git a/crates/apollo-smith/src/next/README.md b/crates/apollo-smith/src/next/README.md new file mode 100644 index 000000000..59853b1c0 --- /dev/null +++ b/crates/apollo-smith/src/next/README.md @@ -0,0 +1,2 @@ +# Apollo-Smith Next +The next folder **** diff --git a/crates/apollo-smith/src/next/mod.rs b/crates/apollo-smith/src/next/mod.rs index dd3d6b23a..42401f8c8 100644 --- a/crates/apollo-smith/src/next/mod.rs +++ b/crates/apollo-smith/src/next/mod.rs @@ -30,7 +30,7 @@ pub enum Error { }, #[error("validation passed, but should have failed")] - ExpectedValidationFail { doc: Document, mutation: String }, + SchemaExpectedValidationFail { doc: Document, mutation: String }, #[error("the serialized AST did not round trip to an identical AST")] SerializationInconsistency { original: Document, new: Document }, @@ -38,8 +38,22 @@ pub enum Error { #[error("parse error")] Parse(WithErrors), + #[error("validation passed, but should have failed")] + ExecutableExpectedValidationFail { + schema: Valid, + doc: Document, + mutation: String + }, + #[error("reparse error")] - Reparse { + SchemaReparse { + doc: Document, + errors: WithErrors, + }, + + #[error("reparse error")] + ExecutableReparse { + schema: Valid, doc: Document, errors: WithErrors, }, @@ -67,12 +81,13 @@ pub fn generate_schema_document(u: &mut Unstructured) -> Result continue; } + // Now let's validate that the schema says it's OK match (mutation.is_valid(), new_doc.to_schema_validate()) { (true, Ok(new_schema)) => { // Let's reparse the document to check that it can be parsed let reparsed = Document::parse(new_doc.to_string(), PathBuf::from("synthetic")) - .map_err(|e| Error::Reparse { + .map_err(|e| Error::SchemaReparse { doc: new_doc.clone(), errors: e, })?; @@ -87,7 +102,6 @@ pub fn generate_schema_document(u: &mut Unstructured) -> Result // Let's try and create an executable document from the schema generate_executable_document(u, &new_schema)?; - schema = new_schema.into_inner(); doc = new_doc; continue; @@ -99,7 +113,7 @@ pub fn generate_schema_document(u: &mut Unstructured) -> Result }); } (false, Ok(_)) => { - return Err(Error::ExpectedValidationFail { + return Err(Error::SchemaExpectedValidationFail { doc: new_doc, mutation: mutation.type_name().to_string(), }); @@ -119,7 +133,7 @@ pub(crate) fn generate_executable_document( ) -> Result { let mut doc = Document::new(); let mutations = mutations::executable_document_mutations(); - for _ in 0..1000 { + for count in 0..1000 { if u.len() == 0 { // We ran out of data abort. This is not an error return Err(Error::Arbitrary(arbitrary::Error::NotEnoughData))?; @@ -133,12 +147,12 @@ pub(crate) fn generate_executable_document( } // Now let's validate that the schema says it's OK - match (mutation.is_valid(), new_doc.to_executable_validate(schema)) { (true, Ok(_)) => { // Let's reparse the document to check that it can be parsed let reparsed = Document::parse(new_doc.to_string(), PathBuf::from("synthetic")) - .map_err(|e| Error::Reparse { + .map_err(|e| Error::ExecutableReparse { + schema: schema.clone(), doc: new_doc.clone(), errors: e, })?; @@ -162,7 +176,7 @@ pub(crate) fn generate_executable_document( }); } (false, Ok(_)) => { - return Err(Error::ExpectedValidationFail { + return Err(Error::SchemaExpectedValidationFail { doc: new_doc, mutation: mutation.type_name().to_string(), }); diff --git a/crates/apollo-smith/src/next/mutations/add_operation_definition.rs b/crates/apollo-smith/src/next/mutations/add_operation_definition.rs index 3bfb6feec..366126bc0 100644 --- a/crates/apollo-smith/src/next/mutations/add_operation_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_operation_definition.rs @@ -17,9 +17,9 @@ impl Mutation for AddOperationDefinition { .push(Definition::OperationDefinition(Node::new( u.arbitrary_operation_definition(schema)?, ))); - Ok(false) + Ok(true) } fn is_valid(&self) -> bool { - false + true } } diff --git a/crates/apollo-smith/src/next/mutations/mod.rs b/crates/apollo-smith/src/next/mutations/mod.rs index 67cbb80ec..407082881 100644 --- a/crates/apollo-smith/src/next/mutations/mod.rs +++ b/crates/apollo-smith/src/next/mutations/mod.rs @@ -48,7 +48,7 @@ pub(crate) fn schema_mutations() -> Vec> { Box::new(AddInputObjectTypeDefinition), Box::new(AddUnionTypeDefinition), Box::new(AddInterfaceTypeDefinition), - Box::new(RemoveAllFields), + //Box::new(RemoveAllFields), Box::new(RemoveRequiredField), ] } diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index 92380bea2..5f71640ad 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -16,6 +16,8 @@ use crate::next::ast::directive_definition::DirectiveDefinitionIterExt; use crate::next::schema::extended_type::{ExtendedTypeExt, ExtendedTypeKind}; use crate::next::schema::object_type::ObjectTypeExt; use crate::next::schema::schema::SchemaExt; +use crate::next::schema::TypeHasFields; + pub struct Unstructured<'a> { u: arbitrary::Unstructured<'a>, counter: usize, @@ -447,8 +449,10 @@ impl Unstructured<'_> { .sample_directives(self)? .into_iter() .with_location(DirectiveLocation::Field) + // skip and include are not allowed on operations + .filter(|d|d.name != "skip" && d.name != "include") .try_collect(self, schema)?, - selection_set: self.arbitrary_vec(0, 5, |u| { + selection_set: self.arbitrary_vec(1, 5, |u| { Ok(u.arbitrary_selection(schema, operation.deref())?) })?, }) @@ -484,7 +488,7 @@ impl Unstructured<'_> { .into_iter() .with_location(DirectiveLocation::InlineFragment) .try_collect(self, schema)?, - selection_set: self.arbitrary_vec(0, 5, |u| Ok(u.arbitrary_selection(schema, ty)?))?, + selection_set: self.arbitrary_vec(1, 5, |u| Ok(u.arbitrary_selection(schema, ty)?))?, }) } @@ -502,22 +506,22 @@ impl Unstructured<'_> { fn arbitrary_selection( &mut self, schema: &Schema, - ty: &dyn super::schema::TypeHasFields, + ty: &dyn TypeHasFields, ) -> Result { match self.choose_index(3) { Ok(0) => { let field = ty.random_field(self)?; let field_ty = schema.types.get(field.ty.inner_named_type()).expect("type must exist"); - let selection_set = if field_ty.is_object() || field_ty.is_interface() { - self.arbitrary_vec(0, 5, |u| { + let selection_set = if field_ty.is_scalar() { + vec![] + } else { + self.arbitrary_vec(1, 5, |u| { Ok(u.arbitrary_selection(schema, field_ty)?) })? - } else { - vec![] }; Ok(Selection::Field(Node::new(Field { alias: self.arbitrary_optional(|u|Ok(u.unique_name()))?, - name: self.unique_name(), + name: field.name.clone(), arguments: self.arbitrary_vec(0, 5, |u| { Ok(Node::new(Argument { name: u.unique_name(), diff --git a/fuzz/fuzz_targets/parser_next.rs b/fuzz/fuzz_targets/parser_next.rs index d164adb05..4bde7f942 100644 --- a/fuzz/fuzz_targets/parser_next.rs +++ b/fuzz/fuzz_targets/parser_next.rs @@ -20,7 +20,7 @@ fuzz_target!(|data: &[u8]| { Error::Parse(doc) => { println!("{}\ndoc:\n{}\nerrors:\n{}", e, doc.to_string(), doc.errors); } - Error::ExpectedValidationFail { doc, mutation } => { + Error::SchemaExpectedValidationFail { doc, mutation } => { println!("{}\nmutation:\n{}\ndoc:\n{}", e, mutation, doc.to_string()); } Error::SerializationInconsistency { original, new } => { @@ -39,7 +39,7 @@ fuzz_target!(|data: &[u8]| { errors.errors ); } - Error::Reparse { doc, errors } => { + Error::SchemaReparse { doc, errors } => { println!( "{}\ndoc:\n{}\nerrors:\n{}", e, @@ -47,19 +47,36 @@ fuzz_target!(|data: &[u8]| { errors.errors ); } + + Error::ExecutableReparse { + schema, + doc, + errors, + } => { + println!( + "{}\ndoc:\n{}\nschema:\n{}\nerrors:\n{}", + e, + doc.to_string(), + schema.to_string(), + errors.errors + ); + } Error::ExecutableDocumentValidation { doc, schema, errors, } => { println!( - "{}\nschena\n{}\ndoc:\n{}\nerrors:\n{}", + "{}\ndoc\n{}\nschema:\n{}\nerrors:\n{}", e, - schema.to_string(), doc.to_string(), + schema.to_string(), errors.errors ); } + Error::ExecutableExpectedValidationFail { schema, doc, mutation } => { + println!("{}\nmutation:\n{}\ndoc:\n{}\nschema\n{}", e, mutation, doc.to_string(), schema.to_string()); + } } panic!("error detected: {}", e); } From 90c1e73b817f488ac6e0e4513cd45b58d266c9dd Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Thu, 14 Mar 2024 22:58:33 +0000 Subject: [PATCH 13/23] temp --- crates/apollo-smith/src/next/mod.rs | 8 +-- .../add_anonymous_operation_definition.rs | 30 +++++++++++ .../mutations/add_directive_definition.rs | 4 +- .../mutations/add_enum_type_definition.rs | 4 +- .../add_input_object_type_definition.rs | 4 +- .../add_interface_type_definition.rs | 4 +- .../add_named_operation_definition.rs | 31 +++++++++++ .../mutations/add_object_type_definition.rs | 4 +- .../mutations/add_operation_definition.rs | 25 --------- .../next/mutations/add_schema_definition.rs | 4 +- .../mutations/add_union_type_definition.rs | 4 +- crates/apollo-smith/src/next/mutations/mod.rs | 34 +++++++++--- .../src/next/mutations/remove_all_fields.rs | 4 +- .../next/mutations/remove_required_field.rs | 4 +- crates/apollo-smith/src/next/schema/schema.rs | 1 + crates/apollo-smith/src/next/unstructured.rs | 54 +++++++++++-------- 16 files changed, 144 insertions(+), 75 deletions(-) create mode 100644 crates/apollo-smith/src/next/mutations/add_anonymous_operation_definition.rs create mode 100644 crates/apollo-smith/src/next/mutations/add_named_operation_definition.rs delete mode 100644 crates/apollo-smith/src/next/mutations/add_operation_definition.rs diff --git a/crates/apollo-smith/src/next/mod.rs b/crates/apollo-smith/src/next/mod.rs index 42401f8c8..4c5a7db85 100644 --- a/crates/apollo-smith/src/next/mod.rs +++ b/crates/apollo-smith/src/next/mod.rs @@ -132,8 +132,9 @@ pub(crate) fn generate_executable_document( schema: &Valid, ) -> Result { let mut doc = Document::new(); + let mut executable_document = doc.to_executable(schema).expect("initial document must be valid"); let mutations = mutations::executable_document_mutations(); - for count in 0..1000 { + for _ in 0..1000 { if u.len() == 0 { // We ran out of data abort. This is not an error return Err(Error::Arbitrary(arbitrary::Error::NotEnoughData))?; @@ -141,14 +142,14 @@ pub(crate) fn generate_executable_document( let mutation = u.choose(&mutations)?; let mut new_doc = doc.clone(); // First let's modify the document. - if !mutation.apply(u, &mut new_doc, &schema)? { + if !mutation.apply(u, &mut new_doc, &schema, &executable_document)? { // The mutation didn't apply, let's try another one continue; } // Now let's validate that the schema says it's OK match (mutation.is_valid(), new_doc.to_executable_validate(schema)) { - (true, Ok(_)) => { + (true, Ok(new_executable_document)) => { // Let's reparse the document to check that it can be parsed let reparsed = Document::parse(new_doc.to_string(), PathBuf::from("synthetic")) .map_err(|e| Error::ExecutableReparse { @@ -166,6 +167,7 @@ pub(crate) fn generate_executable_document( } doc = new_doc; + executable_document = new_executable_document.into_inner(); continue; } (true, Err(e)) => { diff --git a/crates/apollo-smith/src/next/mutations/add_anonymous_operation_definition.rs b/crates/apollo-smith/src/next/mutations/add_anonymous_operation_definition.rs new file mode 100644 index 000000000..ecf7bf3a6 --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/add_anonymous_operation_definition.rs @@ -0,0 +1,30 @@ +use crate::next::mutations::{ExecutableDocumentMutation, SchemaMutation}; + +use crate::next::unstructured::Unstructured; +use apollo_compiler::ast::{Definition, Document}; +use apollo_compiler::{ExecutableDocument, Node, Schema}; + +pub(crate) struct AddAnonymousOperationDefinition; + +impl ExecutableDocumentMutation for AddAnonymousOperationDefinition { + fn apply( + &self, + u: &mut Unstructured, + doc: &mut Document, + schema: &Schema, + executable_document: &ExecutableDocument, + ) -> arbitrary::Result { + if !executable_document.named_operations.is_empty() || executable_document.anonymous_operation.is_some() { + // We already have an operation, so we can't add an anonymous one + return Ok(false); + } + doc.definitions + .push(Definition::OperationDefinition(Node::new( + u.arbitrary_operation_definition(schema, executable_document, None)?, + ))); + Ok(true) + } + fn is_valid(&self) -> bool { + true + } +} diff --git a/crates/apollo-smith/src/next/mutations/add_directive_definition.rs b/crates/apollo-smith/src/next/mutations/add_directive_definition.rs index 916e5b3a8..5464e26f2 100644 --- a/crates/apollo-smith/src/next/mutations/add_directive_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_directive_definition.rs @@ -1,11 +1,11 @@ use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; -use crate::next::mutations::Mutation; +use crate::next::mutations::SchemaMutation; use crate::next::unstructured::Unstructured; pub(crate) struct AddDirectiveDefinition; -impl Mutation for AddDirectiveDefinition { +impl SchemaMutation for AddDirectiveDefinition { fn apply( &self, u: &mut Unstructured, diff --git a/crates/apollo-smith/src/next/mutations/add_enum_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_enum_type_definition.rs index dc9fb4b0d..359ff5cd8 100644 --- a/crates/apollo-smith/src/next/mutations/add_enum_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_enum_type_definition.rs @@ -1,11 +1,11 @@ use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; -use crate::next::mutations::Mutation; +use crate::next::mutations::SchemaMutation; use crate::next::unstructured::Unstructured; pub(crate) struct AddEnumTypeDefinition; -impl Mutation for AddEnumTypeDefinition { +impl SchemaMutation for AddEnumTypeDefinition { fn apply( &self, u: &mut Unstructured, diff --git a/crates/apollo-smith/src/next/mutations/add_input_object_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_input_object_type_definition.rs index d269a6456..8892cb92f 100644 --- a/crates/apollo-smith/src/next/mutations/add_input_object_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_input_object_type_definition.rs @@ -1,11 +1,11 @@ use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; -use crate::next::mutations::Mutation; +use crate::next::mutations::SchemaMutation; use crate::next::unstructured::Unstructured; pub(crate) struct AddInputObjectTypeDefinition; -impl Mutation for AddInputObjectTypeDefinition { +impl SchemaMutation for AddInputObjectTypeDefinition { fn apply( &self, u: &mut Unstructured, diff --git a/crates/apollo-smith/src/next/mutations/add_interface_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_interface_type_definition.rs index 4fc7b27fa..aaecb06fb 100644 --- a/crates/apollo-smith/src/next/mutations/add_interface_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_interface_type_definition.rs @@ -1,11 +1,11 @@ use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; -use crate::next::mutations::Mutation; +use crate::next::mutations::SchemaMutation; use crate::next::unstructured::Unstructured; pub(crate) struct AddInterfaceTypeDefinition; -impl Mutation for AddInterfaceTypeDefinition { +impl SchemaMutation for AddInterfaceTypeDefinition { fn apply( &self, u: &mut Unstructured, diff --git a/crates/apollo-smith/src/next/mutations/add_named_operation_definition.rs b/crates/apollo-smith/src/next/mutations/add_named_operation_definition.rs new file mode 100644 index 000000000..0ef82933d --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/add_named_operation_definition.rs @@ -0,0 +1,31 @@ +use crate::next::mutations::{ExecutableDocumentMutation, SchemaMutation}; + +use crate::next::unstructured::Unstructured; +use apollo_compiler::ast::{Definition, Document}; +use apollo_compiler::{ExecutableDocument, Node, Schema}; + +pub(crate) struct AddNamedOperationDefinition; + +impl ExecutableDocumentMutation for AddNamedOperationDefinition { + fn apply( + &self, + u: &mut Unstructured, + doc: &mut Document, + schema: &Schema, + executable_document: &ExecutableDocument, + ) -> arbitrary::Result { + if executable_document.anonymous_operation.is_some() { + // We already have an anonymous operation, so we can't add a named one + return Ok(false); + } + let name = u.unique_name(); + doc.definitions + .push(Definition::OperationDefinition(Node::new( + u.arbitrary_operation_definition(schema, executable_document, Some(name))?, + ))); + Ok(true) + } + fn is_valid(&self) -> bool { + true + } +} diff --git a/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs index c9e2027b6..63e893be1 100644 --- a/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_object_type_definition.rs @@ -1,11 +1,11 @@ use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; -use crate::next::mutations::Mutation; +use crate::next::mutations::SchemaMutation; use crate::next::unstructured::Unstructured; pub(crate) struct AddObjectTypeDefinition; -impl Mutation for AddObjectTypeDefinition { +impl SchemaMutation for AddObjectTypeDefinition { fn apply( &self, u: &mut Unstructured, diff --git a/crates/apollo-smith/src/next/mutations/add_operation_definition.rs b/crates/apollo-smith/src/next/mutations/add_operation_definition.rs deleted file mode 100644 index 366126bc0..000000000 --- a/crates/apollo-smith/src/next/mutations/add_operation_definition.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::next::mutations::Mutation; - -use crate::next::unstructured::Unstructured; -use apollo_compiler::ast::{Definition, Document}; -use apollo_compiler::{Node, Schema}; - -pub(crate) struct AddOperationDefinition; - -impl Mutation for AddOperationDefinition { - fn apply( - &self, - u: &mut Unstructured, - doc: &mut Document, - schema: &Schema, - ) -> arbitrary::Result { - doc.definitions - .push(Definition::OperationDefinition(Node::new( - u.arbitrary_operation_definition(schema)?, - ))); - Ok(true) - } - fn is_valid(&self) -> bool { - true - } -} diff --git a/crates/apollo-smith/src/next/mutations/add_schema_definition.rs b/crates/apollo-smith/src/next/mutations/add_schema_definition.rs index 1055e6e88..64be9826d 100644 --- a/crates/apollo-smith/src/next/mutations/add_schema_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_schema_definition.rs @@ -1,11 +1,11 @@ use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; -use crate::next::mutations::Mutation; +use crate::next::mutations::SchemaMutation; use crate::next::unstructured::Unstructured; pub(crate) struct AddSchemaDefiniton; -impl Mutation for AddSchemaDefiniton { +impl SchemaMutation for AddSchemaDefiniton { fn apply( &self, u: &mut Unstructured, diff --git a/crates/apollo-smith/src/next/mutations/add_union_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_union_type_definition.rs index 4d0858d5a..91befd0fd 100644 --- a/crates/apollo-smith/src/next/mutations/add_union_type_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_union_type_definition.rs @@ -1,11 +1,11 @@ use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::{Node, Schema}; -use crate::next::mutations::Mutation; +use crate::next::mutations::SchemaMutation; use crate::next::unstructured::Unstructured; pub(crate) struct AddUnionTypeDefinition; -impl Mutation for AddUnionTypeDefinition { +impl SchemaMutation for AddUnionTypeDefinition { fn apply( &self, u: &mut Unstructured, diff --git a/crates/apollo-smith/src/next/mutations/mod.rs b/crates/apollo-smith/src/next/mutations/mod.rs index 407082881..7bf8bc6c9 100644 --- a/crates/apollo-smith/src/next/mutations/mod.rs +++ b/crates/apollo-smith/src/next/mutations/mod.rs @@ -1,13 +1,14 @@ use std::any::type_name; use apollo_compiler::ast::Document; -use apollo_compiler::Schema; +use apollo_compiler::{ExecutableDocument, Schema}; +use crate::next::mutations::add_anonymous_operation_definition::AddAnonymousOperationDefinition; use crate::next::mutations::add_directive_definition::AddDirectiveDefinition; use crate::next::mutations::add_input_object_type_definition::AddInputObjectTypeDefinition; use crate::next::mutations::add_interface_type_definition::AddInterfaceTypeDefinition; use crate::next::mutations::add_object_type_definition::AddObjectTypeDefinition; -use crate::next::mutations::add_operation_definition::AddOperationDefinition; +use crate::next::mutations::add_named_operation_definition::AddNamedOperationDefinition; use crate::next::mutations::add_union_type_definition::AddUnionTypeDefinition; use crate::next::mutations::remove_all_fields::RemoveAllFields; use crate::next::mutations::remove_required_field::RemoveRequiredField; @@ -18,13 +19,14 @@ mod add_enum_type_definition; mod add_input_object_type_definition; mod add_interface_type_definition; mod add_object_type_definition; -mod add_operation_definition; +mod add_named_operation_definition; mod add_schema_definition; mod add_union_type_definition; mod remove_all_fields; mod remove_required_field; +mod add_anonymous_operation_definition; -pub(crate) trait Mutation { +pub(crate) trait SchemaMutation { /// Apply the mutation to the document /// Returns false if the mutation did not apply fn apply( @@ -40,7 +42,25 @@ pub(crate) trait Mutation { } } -pub(crate) fn schema_mutations() -> Vec> { + +pub(crate) trait ExecutableDocumentMutation { + /// Apply the mutation to the document + /// Returns false if the mutation did not apply + fn apply( + &self, + u: &mut Unstructured, + doc: &mut Document, + schema: &Schema, + executable: &ExecutableDocument, + ) -> arbitrary::Result; + fn is_valid(&self) -> bool; + + fn type_name(&self) -> &str { + type_name::() + } +} + +pub(crate) fn schema_mutations() -> Vec> { vec![ Box::new(AddObjectTypeDefinition), Box::new(AddInterfaceTypeDefinition), @@ -53,6 +73,6 @@ pub(crate) fn schema_mutations() -> Vec> { ] } -pub(crate) fn executable_document_mutations() -> Vec> { - vec![Box::new(AddOperationDefinition)] +pub(crate) fn executable_document_mutations() -> Vec> { + vec![Box::new(AddNamedOperationDefinition), Box::new(AddAnonymousOperationDefinition)] } diff --git a/crates/apollo-smith/src/next/mutations/remove_all_fields.rs b/crates/apollo-smith/src/next/mutations/remove_all_fields.rs index 3f4522f7b..da492876e 100644 --- a/crates/apollo-smith/src/next/mutations/remove_all_fields.rs +++ b/crates/apollo-smith/src/next/mutations/remove_all_fields.rs @@ -1,4 +1,4 @@ -use crate::next::mutations::Mutation; +use crate::next::mutations::SchemaMutation; use crate::next::ast::definition::DefinitionKind; use crate::next::ast::document::DocumentExt; @@ -7,7 +7,7 @@ use apollo_compiler::ast::{Definition, Document}; use apollo_compiler::Schema; pub(crate) struct RemoveAllFields; -impl Mutation for RemoveAllFields { +impl SchemaMutation for RemoveAllFields { fn apply( &self, u: &mut Unstructured, diff --git a/crates/apollo-smith/src/next/mutations/remove_required_field.rs b/crates/apollo-smith/src/next/mutations/remove_required_field.rs index e5c7daf71..2d31cecf5 100644 --- a/crates/apollo-smith/src/next/mutations/remove_required_field.rs +++ b/crates/apollo-smith/src/next/mutations/remove_required_field.rs @@ -1,4 +1,4 @@ -use crate::next::mutations::Mutation; +use crate::next::mutations::SchemaMutation; use crate::next::ast::definition::DefinitionKind; use crate::next::ast::document::DocumentExt; @@ -9,7 +9,7 @@ use apollo_compiler::Schema; pub(crate) struct RemoveRequiredField; -impl Mutation for RemoveRequiredField { +impl SchemaMutation for RemoveRequiredField { fn apply( &self, u: &mut Unstructured, diff --git a/crates/apollo-smith/src/next/schema/schema.rs b/crates/apollo-smith/src/next/schema/schema.rs index 3a6ff45d4..a0e2dbb92 100644 --- a/crates/apollo-smith/src/next/schema/schema.rs +++ b/crates/apollo-smith/src/next/schema/schema.rs @@ -79,6 +79,7 @@ pub(crate) trait SchemaExt { .types .values() .filter(|d| types.iter().any(|t| t.matches(*d))) + .filter(|d| d.is_scalar() || !d.is_built_in()) .collect::>(); Ok(u.choose(definitions.as_slice()).map_err(|e| { if let arbitrary::Error::EmptyChoose = e { diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index 5f71640ad..9b901a01f 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -9,8 +9,8 @@ use apollo_compiler::ast::{ OperationType, SchemaDefinition, Selection, Type, UnionTypeDefinition, Value, VariableDefinition, }; -use apollo_compiler::schema::{ExtendedType, InterfaceType, }; -use apollo_compiler::{Node, NodeStr, Schema}; +use apollo_compiler::schema::{Component, ExtendedType, InterfaceType}; +use apollo_compiler::{ExecutableDocument, Node, NodeStr, Schema}; use crate::next::ast::directive_definition::DirectiveDefinitionIterExt; use crate::next::schema::extended_type::{ExtendedTypeExt, ExtendedTypeKind}; @@ -435,15 +435,16 @@ impl Unstructured<'_> { pub(crate) fn arbitrary_operation_definition( &mut self, schema: &Schema, + executable_document: &ExecutableDocument, + name: Option, ) -> Result { let operation = schema.random_query_mutation_subscription(self)?; - Ok(OperationDefinition { operation_type: operation .deref() .operation_type() .expect("top level operation must have type"), - name: self.arbitrary_optional(|u| Ok(u.unique_name()))?, + name, variables: vec![], directives: schema .sample_directives(self)? @@ -453,7 +454,7 @@ impl Unstructured<'_> { .filter(|d|d.name != "skip" && d.name != "include") .try_collect(self, schema)?, selection_set: self.arbitrary_vec(1, 5, |u| { - Ok(u.arbitrary_selection(schema, operation.deref())?) + Ok(u.arbitrary_selection(schema, operation.deref(), executable_document)?) })?, }) } @@ -475,7 +476,7 @@ impl Unstructured<'_> { }) } - fn arbitrary_inline_fragment(&mut self, schema: &Schema) -> Result { + fn arbitrary_inline_fragment(&mut self, schema: &Schema, executable_document: &ExecutableDocument) -> Result { let ty = schema.random_type( self, vec![ExtendedTypeKind::Object, ExtendedTypeKind::Interface], @@ -488,13 +489,15 @@ impl Unstructured<'_> { .into_iter() .with_location(DirectiveLocation::InlineFragment) .try_collect(self, schema)?, - selection_set: self.arbitrary_vec(1, 5, |u| Ok(u.arbitrary_selection(schema, ty)?))?, + selection_set: self.arbitrary_vec(1, 5, |u| Ok(u.arbitrary_selection(schema, ty, executable_document)?))?, }) } - fn arbitrary_fragment_spread(&mut self, schema: &Schema) -> Result { + fn arbitrary_fragment_spread(&mut self, schema: &Schema, executable_document: &ExecutableDocument) -> Result { + let definitions = executable_document.fragments.values().collect::>(); + Ok(FragmentSpread { - fragment_name: self.unique_name(), + fragment_name: self.choose(&definitions)?.name.clone(), directives: schema .sample_directives(self)? .into_iter() @@ -507,27 +510,24 @@ impl Unstructured<'_> { &mut self, schema: &Schema, ty: &dyn TypeHasFields, + executable_document: &ExecutableDocument, ) -> Result { - match self.choose_index(3) { - Ok(0) => { + let has_fragments = executable_document.fragments.is_empty(); + match (has_fragments, self.choose_index(3)) { + (_, Ok(0)) | (true, Ok(1)) => { let field = ty.random_field(self)?; let field_ty = schema.types.get(field.ty.inner_named_type()).expect("type must exist"); let selection_set = if field_ty.is_scalar() { vec![] } else { self.arbitrary_vec(1, 5, |u| { - Ok(u.arbitrary_selection(schema, field_ty)?) + Ok(u.arbitrary_selection(schema, field_ty, executable_document)?) })? }; Ok(Selection::Field(Node::new(Field { alias: self.arbitrary_optional(|u|Ok(u.unique_name()))?, name: field.name.clone(), - arguments: self.arbitrary_vec(0, 5, |u| { - Ok(Node::new(Argument { - name: u.unique_name(), - value: Node::new(u.arbitrary_value(schema, &field.ty)?), - })) - })?, + arguments: self.arbitrary_arguments(schema, field)?, directives: schema.sample_directives(self)? .into_iter() .with_location(DirectiveLocation::Field) @@ -535,16 +535,26 @@ impl Unstructured<'_> { selection_set, }))) }, - Ok(1) => Ok(Selection::FragmentSpread(Node::new( - self.arbitrary_fragment_spread(schema)?, + (_, Ok(1)) => Ok(Selection::FragmentSpread(Node::new( + self.arbitrary_fragment_spread(schema, executable_document)?, ))), - Ok(2) => Ok(Selection::InlineFragment(Node::new( - self.arbitrary_inline_fragment(schema)?, + (_, Ok(2)) => Ok(Selection::InlineFragment(Node::new( + self.arbitrary_inline_fragment(schema, executable_document)?, ))), _ => unreachable!(), } } + fn arbitrary_arguments(&mut self, schema: &Schema, field_definition: &FieldDefinition) -> Result>> { + let mut args = Vec::new(); + for arg in &field_definition.arguments { + args.push(Node::new(Argument { + name: arg.name.clone(), + value: Node::new(self.arbitrary_value(schema, &field_definition.ty)?), + })); + } + Ok(args) + } } impl<'a> Deref for Unstructured<'a> { From 96e0ed69840240806ac41858ea8b9252fc0f8f6f Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Thu, 14 Mar 2024 23:09:42 +0000 Subject: [PATCH 14/23] Trying to fix fragments --- crates/apollo-smith/src/next/unstructured.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index 9b901a01f..5baf17b07 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -476,7 +476,7 @@ impl Unstructured<'_> { }) } - fn arbitrary_inline_fragment(&mut self, schema: &Schema, executable_document: &ExecutableDocument) -> Result { + fn arbitrary_inline_fragment(&mut self, schema: &Schema, ty: &dyn TypeHasFields, executable_document: &ExecutableDocument) -> Result { let ty = schema.random_type( self, vec![ExtendedTypeKind::Object, ExtendedTypeKind::Interface], @@ -493,7 +493,7 @@ impl Unstructured<'_> { }) } - fn arbitrary_fragment_spread(&mut self, schema: &Schema, executable_document: &ExecutableDocument) -> Result { + fn arbitrary_fragment_spread(&mut self, schema: &Schema, ty: &dyn TypeHasFields,executable_document: &ExecutableDocument) -> Result { let definitions = executable_document.fragments.values().collect::>(); Ok(FragmentSpread { @@ -536,10 +536,10 @@ impl Unstructured<'_> { }))) }, (_, Ok(1)) => Ok(Selection::FragmentSpread(Node::new( - self.arbitrary_fragment_spread(schema, executable_document)?, + self.arbitrary_fragment_spread(schema, ty, executable_document)?, ))), (_, Ok(2)) => Ok(Selection::InlineFragment(Node::new( - self.arbitrary_inline_fragment(schema, executable_document)?, + self.arbitrary_inline_fragment(schema, ty, executable_document)?, ))), _ => unreachable!(), } From 601cdb87edd251f9477451cea80fdb9eb5f40792 Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Sun, 17 Mar 2024 20:43:25 +0000 Subject: [PATCH 15/23] temp --- .../next/executable/executable_document.rs | 30 ++++ .../apollo-smith/src/next/executable/mod.rs | 1 + crates/apollo-smith/src/next/mod.rs | 1 + .../add_anonymous_operation_definition.rs | 2 +- .../next/mutations/add_fragment_definiton.rs | 26 +++ .../add_named_operation_definition.rs | 2 +- crates/apollo-smith/src/next/mutations/mod.rs | 1 + crates/apollo-smith/src/next/schema/mod.rs | 101 +++++++++++- crates/apollo-smith/src/next/unstructured.rs | 150 ++++++++++++------ 9 files changed, 254 insertions(+), 60 deletions(-) create mode 100644 crates/apollo-smith/src/next/executable/executable_document.rs create mode 100644 crates/apollo-smith/src/next/executable/mod.rs create mode 100644 crates/apollo-smith/src/next/mutations/add_fragment_definiton.rs diff --git a/crates/apollo-smith/src/next/executable/executable_document.rs b/crates/apollo-smith/src/next/executable/executable_document.rs new file mode 100644 index 000000000..87d914a36 --- /dev/null +++ b/crates/apollo-smith/src/next/executable/executable_document.rs @@ -0,0 +1,30 @@ +use crate::next::Unstructured; +use apollo_compiler::executable::Fragment; +use apollo_compiler::{ExecutableDocument, Node}; +use crate::next::schema::Selectable; + +pub(crate) trait ExecutableDocumentExt { + fn random_fragment(&self, u: &mut Unstructured) -> arbitrary::Result<&Node> { + let fragments = self.target().fragments.values().collect::>(); + Ok(fragments[u.choose_index(fragments.len())?]) + } + + fn random_fragment_of_type( + &self, + u: &mut Unstructured, + selectable: &impl Selectable + ) -> arbitrary::Result>> { + let fragments = self.target().fragments.values().filter(|f|&f.selection_set.ty == selectable.name()).collect::>(); + if fragments.is_empty() { + return Ok(None) + } + Ok(Some(fragments[u.choose_index(fragments.len())?])) + } + fn target(&self) -> &ExecutableDocument; +} + +impl ExecutableDocumentExt for ExecutableDocument { + fn target(&self) -> &ExecutableDocument { + &self + } +} diff --git a/crates/apollo-smith/src/next/executable/mod.rs b/crates/apollo-smith/src/next/executable/mod.rs new file mode 100644 index 000000000..318745448 --- /dev/null +++ b/crates/apollo-smith/src/next/executable/mod.rs @@ -0,0 +1 @@ +pub(crate) mod executable_document; diff --git a/crates/apollo-smith/src/next/mod.rs b/crates/apollo-smith/src/next/mod.rs index 4c5a7db85..2ddb68e68 100644 --- a/crates/apollo-smith/src/next/mod.rs +++ b/crates/apollo-smith/src/next/mod.rs @@ -10,6 +10,7 @@ mod ast; mod mutations; mod schema; mod unstructured; +mod executable; #[derive(thiserror::Error, Debug)] pub enum Error { diff --git a/crates/apollo-smith/src/next/mutations/add_anonymous_operation_definition.rs b/crates/apollo-smith/src/next/mutations/add_anonymous_operation_definition.rs index ecf7bf3a6..846f6aec0 100644 --- a/crates/apollo-smith/src/next/mutations/add_anonymous_operation_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_anonymous_operation_definition.rs @@ -1,4 +1,4 @@ -use crate::next::mutations::{ExecutableDocumentMutation, SchemaMutation}; +use crate::next::mutations::{ExecutableDocumentMutation}; use crate::next::unstructured::Unstructured; use apollo_compiler::ast::{Definition, Document}; diff --git a/crates/apollo-smith/src/next/mutations/add_fragment_definiton.rs b/crates/apollo-smith/src/next/mutations/add_fragment_definiton.rs new file mode 100644 index 000000000..13b340475 --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/add_fragment_definiton.rs @@ -0,0 +1,26 @@ +use crate::next::mutations::{ExecutableDocumentMutation}; + +use crate::next::unstructured::Unstructured; +use apollo_compiler::ast::{Definition, Document}; +use apollo_compiler::{ExecutableDocument, Node, Schema}; + +pub(crate) struct AddFragmentDefiniton; + +impl ExecutableDocumentMutation for AddFragmentDefiniton { + fn apply( + &self, + u: &mut Unstructured, + doc: &mut Document, + schema: &Schema, + executable_document: &ExecutableDocument, + ) -> arbitrary::Result { + doc.definitions + .push(Definition::FragmentDefinition(Node::new( + u.arbitrary_fragment_definition(schema, executable_document)?, + ))); + Ok(true) + } + fn is_valid(&self) -> bool { + true + } +} diff --git a/crates/apollo-smith/src/next/mutations/add_named_operation_definition.rs b/crates/apollo-smith/src/next/mutations/add_named_operation_definition.rs index 0ef82933d..6dc76b107 100644 --- a/crates/apollo-smith/src/next/mutations/add_named_operation_definition.rs +++ b/crates/apollo-smith/src/next/mutations/add_named_operation_definition.rs @@ -1,4 +1,4 @@ -use crate::next::mutations::{ExecutableDocumentMutation, SchemaMutation}; +use crate::next::mutations::{ExecutableDocumentMutation}; use crate::next::unstructured::Unstructured; use apollo_compiler::ast::{Definition, Document}; diff --git a/crates/apollo-smith/src/next/mutations/mod.rs b/crates/apollo-smith/src/next/mutations/mod.rs index 7bf8bc6c9..b212a808b 100644 --- a/crates/apollo-smith/src/next/mutations/mod.rs +++ b/crates/apollo-smith/src/next/mutations/mod.rs @@ -25,6 +25,7 @@ mod add_union_type_definition; mod remove_all_fields; mod remove_required_field; mod add_anonymous_operation_definition; +mod add_fragment_definiton; pub(crate) trait SchemaMutation { /// Apply the mutation to the document diff --git a/crates/apollo-smith/src/next/schema/mod.rs b/crates/apollo-smith/src/next/schema/mod.rs index a2d4c02fd..4010a781a 100644 --- a/crates/apollo-smith/src/next/schema/mod.rs +++ b/crates/apollo-smith/src/next/schema/mod.rs @@ -1,7 +1,9 @@ +use std::ops::Deref; use std::sync::OnceLock; use apollo_compiler::ast::{FieldDefinition, Name}; -use apollo_compiler::schema::{Component, ExtendedType, InterfaceType, ObjectType}; +use apollo_compiler::schema::{Component, ExtendedType, InterfaceType, ObjectType, UnionType}; use indexmap::IndexMap; +use apollo_compiler::Schema; use crate::next::Unstructured; pub(crate) mod extended_type; @@ -71,8 +73,12 @@ macro_rules! field_access { field_access!(ObjectType); field_access!(InterfaceType); -pub(crate) trait TypeHasFields { +pub(crate) trait Selectable { + + fn name(&self) -> &Name; fn fields(&self) -> &IndexMap>; + + fn random_specialization<'a>(&self, u: &mut Unstructured, schema: &'a Schema) -> arbitrary::Result>; fn random_field(&self, u: &mut Unstructured) -> arbitrary::Result<&Component> { // Types always have at least one field let fields = self.fields().values().collect::>(); @@ -81,25 +87,106 @@ pub(crate) trait TypeHasFields { } -impl TypeHasFields for ObjectType { +impl Selectable for ObjectType { + fn name(&self) -> &Name { + &self.name + } + + + fn fields(&self) -> &IndexMap> { &self.fields } + + fn random_specialization<'a>(&self, _u: &mut Unstructured, _schema: &'a Schema) -> arbitrary::Result> { + Ok(None) + } } -impl TypeHasFields for InterfaceType { +impl Selectable for &UnionType { + fn name(&self) -> &Name { + &self.name + } + + fn fields(&self) -> &IndexMap> { + static EMPTY: OnceLock>> = OnceLock::new(); + &EMPTY.get_or_init(||Default::default()) + } + + fn random_specialization<'a>(&self, u: &mut Unstructured, schema: &'a Schema) -> arbitrary::Result> { + let members = self.members.iter().map(|name| schema.types.get(&name.name)).collect::>(); + if members.is_empty() { + Ok(None) + } + else { + Ok(members[u.choose_index(members.len())?]) + } + } +} + +impl Selectable for InterfaceType { + fn name(&self) -> &Name { + &self.name + } + fn fields(&self) -> &IndexMap> { &self.fields } + + fn random_specialization<'a>(&self, u: &mut Unstructured, schema: &'a Schema) -> arbitrary::Result> { + // An interface specialization is either an object or another interface that implements this interface + let implements = schema + .types + .values() + .filter(|ty| { + match ty { + ExtendedType::Object(o) => o.implements_interfaces.contains(&self.name), + ExtendedType::Interface(i) => i.implements_interfaces.contains(&self.name), + _=> return false, + } + }) + .collect::>(); + if implements.is_empty() { + Ok(None) + } + else { + Ok(Some(implements[u.choose_index(implements.len())?])) + } + + + } } -impl TypeHasFields for ExtendedType { + +impl Selectable for ExtendedType { + fn name(&self) -> &Name { + match self { + ExtendedType::Scalar(scalar) => {&scalar.name} + ExtendedType::Object(object_type) => {&object_type.name} + ExtendedType::Interface(interface_type) => {&interface_type.name} + ExtendedType::Union(union_type) => {&union_type.name} + ExtendedType::Enum(enum_type) => {&enum_type.name} + ExtendedType::InputObject(input_object) => {&input_object.name} + } + } + + + fn fields(&self) -> &IndexMap> { static EMPTY: OnceLock>> = OnceLock::new(); match self { - ExtendedType::Object(t) => t.fields(), - ExtendedType::Interface(t) => t.fields(), + ExtendedType::Object(t) => &t.fields, + ExtendedType::Interface(t) => &t.fields, _ => &EMPTY.get_or_init(||Default::default()), } } + + fn random_specialization<'a>(&self, u: &mut Unstructured, schema: &'a Schema) -> arbitrary::Result> { + match self { + ExtendedType::Object(object_type) => {object_type.deref().random_specialization(u, schema)} + ExtendedType::Interface(interface_type) => { interface_type.deref().random_specialization(u, schema)} + ExtendedType::Union(union_type) => { union_type.deref().random_specialization(u, schema)} + _ => Ok(None) + } + } } diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index 5baf17b07..bdd342e7c 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -4,19 +4,20 @@ use arbitrary::Result; use apollo_compiler::ast::{ Argument, DirectiveDefinition, DirectiveLocation, EnumTypeDefinition, EnumValueDefinition, - Field, FieldDefinition, FragmentSpread, InlineFragment, InputObjectTypeDefinition, - InputValueDefinition, InterfaceTypeDefinition, Name, ObjectTypeDefinition, OperationDefinition, - OperationType, SchemaDefinition, Selection, Type, UnionTypeDefinition, Value, - VariableDefinition, + Field, FieldDefinition, FragmentDefinition, FragmentSpread, InlineFragment, + InputObjectTypeDefinition, InputValueDefinition, InterfaceTypeDefinition, Name, + ObjectTypeDefinition, OperationDefinition, OperationType, SchemaDefinition, Selection, Type, + UnionTypeDefinition, Value, VariableDefinition, }; -use apollo_compiler::schema::{Component, ExtendedType, InterfaceType}; +use apollo_compiler::schema::{ExtendedType, InterfaceType}; use apollo_compiler::{ExecutableDocument, Node, NodeStr, Schema}; use crate::next::ast::directive_definition::DirectiveDefinitionIterExt; +use crate::next::executable::executable_document::ExecutableDocumentExt; use crate::next::schema::extended_type::{ExtendedTypeExt, ExtendedTypeKind}; use crate::next::schema::object_type::ObjectTypeExt; use crate::next::schema::schema::SchemaExt; -use crate::next::schema::TypeHasFields; +use crate::next::schema::Selectable; pub struct Unstructured<'a> { u: arbitrary::Unstructured<'a>, @@ -451,7 +452,7 @@ impl Unstructured<'_> { .into_iter() .with_location(DirectiveLocation::Field) // skip and include are not allowed on operations - .filter(|d|d.name != "skip" && d.name != "include") + .filter(|d| d.name != "skip" && d.name != "include") .try_collect(self, schema)?, selection_set: self.arbitrary_vec(1, 5, |u| { Ok(u.arbitrary_selection(schema, operation.deref(), executable_document)?) @@ -476,76 +477,116 @@ impl Unstructured<'_> { }) } - fn arbitrary_inline_fragment(&mut self, schema: &Schema, ty: &dyn TypeHasFields, executable_document: &ExecutableDocument) -> Result { - let ty = schema.random_type( + pub(crate) fn arbitrary_fragment_definition( + &mut self, + schema: &Schema, + executable_document: &ExecutableDocument, + ) -> Result { + let type_condition = schema.random_type( self, - vec![ExtendedTypeKind::Object, ExtendedTypeKind::Interface], + vec![ + ExtendedTypeKind::Object, + ExtendedTypeKind::Interface, + ExtendedTypeKind::Union, + ], )?; - - Ok(InlineFragment { - type_condition: self.arbitrary_optional(|_| Ok(ty.name().clone()))?, + Ok(FragmentDefinition { + name: self.unique_name(), + type_condition: type_condition.name().clone(), directives: schema .sample_directives(self)? .into_iter() - .with_location(DirectiveLocation::InlineFragment) + .with_location(DirectiveLocation::FragmentDefinition) .try_collect(self, schema)?, - selection_set: self.arbitrary_vec(1, 5, |u| Ok(u.arbitrary_selection(schema, ty, executable_document)?))?, + selection_set: self.arbitrary_vec(1, 5, |u| { + Ok(u.arbitrary_selection(schema, type_condition, executable_document)?) + })?, }) } - fn arbitrary_fragment_spread(&mut self, schema: &Schema, ty: &dyn TypeHasFields,executable_document: &ExecutableDocument) -> Result { - let definitions = executable_document.fragments.values().collect::>(); - - Ok(FragmentSpread { - fragment_name: self.choose(&definitions)?.name.clone(), + pub(crate) fn arbitrary_inline_fragment<'a>( + &mut self, + schema: &Schema, + selectable: &impl Selectable, + executable_document: &ExecutableDocument, + ) -> Result { + Ok(InlineFragment { + type_condition: self.arbitrary_optional(|_| Ok(selectable.name().clone()))?, directives: schema .sample_directives(self)? .into_iter() - .with_location(DirectiveLocation::FragmentSpread) + .with_location(DirectiveLocation::InlineFragment) .try_collect(self, schema)?, + selection_set: self.arbitrary_vec(1, 5, |u| { + Ok(u.arbitrary_selection(schema, selectable, executable_document)?) + })?, }) } - fn arbitrary_selection( + fn arbitrary_selection<'a>( &mut self, schema: &Schema, - ty: &dyn TypeHasFields, + selectable: &impl Selectable, executable_document: &ExecutableDocument, ) -> Result { - let has_fragments = executable_document.fragments.is_empty(); - match (has_fragments, self.choose_index(3)) { - (_, Ok(0)) | (true, Ok(1)) => { - let field = ty.random_field(self)?; - let field_ty = schema.types.get(field.ty.inner_named_type()).expect("type must exist"); - let selection_set = if field_ty.is_scalar() { - vec![] - } else { - self.arbitrary_vec(1, 5, |u| { - Ok(u.arbitrary_selection(schema, field_ty, executable_document)?) - })? - }; - Ok(Selection::Field(Node::new(Field { - alias: self.arbitrary_optional(|u|Ok(u.unique_name()))?, - name: field.name.clone(), - arguments: self.arbitrary_arguments(schema, field)?, - directives: schema.sample_directives(self)? + // The selection must contain at least one field, fragment spread or inline fragment + // If the type is a union then it must contain at least one inline fragment + + let selection_type = *self.choose(&vec![ + SelectionType::Field, + SelectionType::InlineFragment, + SelectionType::FragmentSpread, + ])?; + + if selection_type == SelectionType::FragmentSpread { + if let Some(fragment) = executable_document.random_fragment_of_type(self, selectable)? { + return Ok(Selection::FragmentSpread(Node::new(FragmentSpread { + fragment_name: fragment.name.clone(), + directives: schema + .sample_directives(self)? .into_iter() .with_location(DirectiveLocation::Field) .try_collect(self, schema)?, - selection_set, - }))) - }, - (_, Ok(1)) => Ok(Selection::FragmentSpread(Node::new( - self.arbitrary_fragment_spread(schema, ty, executable_document)?, - ))), - (_, Ok(2)) => Ok(Selection::InlineFragment(Node::new( - self.arbitrary_inline_fragment(schema, ty, executable_document)?, - ))), - _ => unreachable!(), + }))); + } + } + if selection_type == SelectionType::InlineFragment { + if let Some(specialization) = selectable.random_specialization(self, schema)? { + return Ok(Selection::InlineFragment(Node::new( + self.arbitrary_inline_fragment(schema, specialization, executable_document)?, + ))); + } } + let field = selectable.random_field(self)?; + let field_ty = schema + .types + .get(field.ty.inner_named_type()) + .expect("type must exist"); + let selection_set = if field_ty.is_scalar() { + vec![] + } else { + self.arbitrary_vec(1, 5, |u| { + Ok(u.arbitrary_selection(schema, field_ty, executable_document)?) + })? + }; + Ok(Selection::Field(Node::new(Field { + alias: self.arbitrary_optional(|u| Ok(u.unique_name()))?, + name: field.name.clone(), + arguments: self.arbitrary_arguments(schema, field)?, + directives: schema + .sample_directives(self)? + .into_iter() + .with_location(DirectiveLocation::Field) + .try_collect(self, schema)?, + selection_set, + }))) } - fn arbitrary_arguments(&mut self, schema: &Schema, field_definition: &FieldDefinition) -> Result>> { + fn arbitrary_arguments( + &mut self, + schema: &Schema, + field_definition: &FieldDefinition, + ) -> Result>> { let mut args = Vec::new(); for arg in &field_definition.arguments { args.push(Node::new(Argument { @@ -557,6 +598,13 @@ impl Unstructured<'_> { } } +#[derive(Copy, Clone, Eq, PartialEq)] +enum SelectionType { + Field, + FragmentSpread, + InlineFragment, +} + impl<'a> Deref for Unstructured<'a> { type Target = arbitrary::Unstructured<'a>; From 467c4e0e77789f30c11bdc287f2059cb9cff1d13 Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Sun, 17 Mar 2024 21:19:29 +0000 Subject: [PATCH 16/23] temp --- crates/apollo-smith/src/next/mod.rs | 3 ++- crates/apollo-smith/src/next/unstructured.rs | 22 ++++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/crates/apollo-smith/src/next/mod.rs b/crates/apollo-smith/src/next/mod.rs index 2ddb68e68..6c6859cbc 100644 --- a/crates/apollo-smith/src/next/mod.rs +++ b/crates/apollo-smith/src/next/mod.rs @@ -135,7 +135,7 @@ pub(crate) fn generate_executable_document( let mut doc = Document::new(); let mut executable_document = doc.to_executable(schema).expect("initial document must be valid"); let mutations = mutations::executable_document_mutations(); - for _ in 0..1000 { + for _ in 0..10 { if u.len() == 0 { // We ran out of data abort. This is not an error return Err(Error::Arbitrary(arbitrary::Error::NotEnoughData))?; @@ -190,6 +190,7 @@ pub(crate) fn generate_executable_document( } } } + println!("Generated executable document: {}\n for schema {}", doc, schema); Ok(doc) } diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index bdd342e7c..ce41c7dd0 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -312,14 +312,14 @@ impl Unstructured<'_> { } fn all_fields_from_interfaces( - implements: &Vec<&Node>, + interfaces: &Vec<&Node>, ) -> Vec> { - let implements_fields = implements + let all_fields = interfaces .iter() .flat_map(|interface| interface.fields.values()) .map(|field| field.deref().clone()) .collect::>(); - implements_fields + all_fields } pub(crate) fn arbitrary_field_definition( @@ -439,9 +439,15 @@ impl Unstructured<'_> { executable_document: &ExecutableDocument, name: Option, ) -> Result { - let operation = schema.random_query_mutation_subscription(self)?; + let object = schema.random_query_mutation_subscription(self)?; + let directive_location = match object.name.as_ref() { + "Query" => DirectiveLocation::Query, + "Mutation" => DirectiveLocation::Mutation, + "Subscription" => DirectiveLocation::Subscription, + _ => panic!("invalid object name"), + }; Ok(OperationDefinition { - operation_type: operation + operation_type: object .deref() .operation_type() .expect("top level operation must have type"), @@ -450,12 +456,10 @@ impl Unstructured<'_> { directives: schema .sample_directives(self)? .into_iter() - .with_location(DirectiveLocation::Field) - // skip and include are not allowed on operations - .filter(|d| d.name != "skip" && d.name != "include") + .with_location(directive_location) .try_collect(self, schema)?, selection_set: self.arbitrary_vec(1, 5, |u| { - Ok(u.arbitrary_selection(schema, operation.deref(), executable_document)?) + Ok(u.arbitrary_selection(schema, object.deref(), executable_document)?) })?, }) } From 027a12681ac480ac7d675f4d6eba6d0d18fafeba Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Sun, 17 Mar 2024 21:42:03 +0000 Subject: [PATCH 17/23] temp --- crates/apollo-smith/src/next/mod.rs | 12 ++++++++++-- crates/apollo-smith/src/next/unstructured.rs | 5 +++++ fuzz/fuzz_targets/parser_next.rs | 18 ++++++++++++------ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/crates/apollo-smith/src/next/mod.rs b/crates/apollo-smith/src/next/mod.rs index 6c6859cbc..1c477ad6d 100644 --- a/crates/apollo-smith/src/next/mod.rs +++ b/crates/apollo-smith/src/next/mod.rs @@ -19,19 +19,21 @@ pub enum Error { #[error("schema document validation failed")] SchemaDocumentValidation { + mutation: String, doc: Document, errors: WithErrors, }, #[error("executable document validation failed")] ExecutableDocumentValidation { + mutation: String, doc: Document, schema: Valid, errors: WithErrors, }, #[error("validation passed, but should have failed")] - SchemaExpectedValidationFail { doc: Document, mutation: String }, + SchemaExpectedValidationFail { mutation: String, doc: Document }, #[error("the serialized AST did not round trip to an identical AST")] SerializationInconsistency { original: Document, new: Document }, @@ -48,12 +50,14 @@ pub enum Error { #[error("reparse error")] SchemaReparse { + mutation: String, doc: Document, errors: WithErrors, }, #[error("reparse error")] ExecutableReparse { + mutation: String, schema: Valid, doc: Document, errors: WithErrors, @@ -89,6 +93,7 @@ pub fn generate_schema_document(u: &mut Unstructured) -> Result // Let's reparse the document to check that it can be parsed let reparsed = Document::parse(new_doc.to_string(), PathBuf::from("synthetic")) .map_err(|e| Error::SchemaReparse { + mutation: mutation.type_name().to_string(), doc: new_doc.clone(), errors: e, })?; @@ -109,6 +114,7 @@ pub fn generate_schema_document(u: &mut Unstructured) -> Result } (true, Err(e)) => { return Err(Error::SchemaDocumentValidation { + mutation: mutation.type_name().to_string(), doc: new_doc, errors: e, }); @@ -154,6 +160,7 @@ pub(crate) fn generate_executable_document( // Let's reparse the document to check that it can be parsed let reparsed = Document::parse(new_doc.to_string(), PathBuf::from("synthetic")) .map_err(|e| Error::ExecutableReparse { + mutation: mutation.type_name().to_string(), schema: schema.clone(), doc: new_doc.clone(), errors: e, @@ -173,6 +180,7 @@ pub(crate) fn generate_executable_document( } (true, Err(e)) => { return Err(Error::ExecutableDocumentValidation { + mutation: mutation.type_name().to_string(), doc: new_doc, schema: schema.clone(), errors: e, @@ -180,8 +188,8 @@ pub(crate) fn generate_executable_document( } (false, Ok(_)) => { return Err(Error::SchemaExpectedValidationFail { - doc: new_doc, mutation: mutation.type_name().to_string(), + doc: new_doc, }); } (false, Err(_)) => { diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index ce41c7dd0..5b34f0330 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -144,6 +144,8 @@ impl Unstructured<'_> { ) -> Result { let implements = schema.sample_interface_types(self)?; let implements_fields = Self::all_fields_from_interfaces(&implements); + println!("implements: {:?}", implements); + println!("implements_fields: {:?}", implements_fields); let new_fields = self.arbitrary_vec(1, 5, |u| { Ok(Node::new(u.arbitrary_field_definition( schema, @@ -285,6 +287,9 @@ impl Unstructured<'_> { // All interfaces need to have all the fields from the interfaces they implement. let implements = schema.sample_interface_types(self)?; let implements_fields = Self::all_fields_from_interfaces(&implements); + println!("implements: {:?}", implements); + println!("implements_fields: {:?}", implements_fields); + let new_fields = self.arbitrary_vec(1, 5, |u| { Ok(Node::new(u.arbitrary_field_definition( schema, diff --git a/fuzz/fuzz_targets/parser_next.rs b/fuzz/fuzz_targets/parser_next.rs index 4bde7f942..33795668e 100644 --- a/fuzz/fuzz_targets/parser_next.rs +++ b/fuzz/fuzz_targets/parser_next.rs @@ -31,44 +31,50 @@ fuzz_target!(|data: &[u8]| { new.to_string() ); } - Error::SchemaDocumentValidation { doc, errors } => { + Error::SchemaDocumentValidation {mutation, doc, errors } => { println!( - "{}\ndoc:\n{}\nerrors:\n{}", + "{}\nmutation:\n{}\ndoc:\n{}\nerrors:\n{}", e, + mutation, doc.to_string(), errors.errors ); } - Error::SchemaReparse { doc, errors } => { + Error::SchemaReparse { mutation, doc, errors } => { println!( - "{}\ndoc:\n{}\nerrors:\n{}", + "{}\nmutation:\n{}\ndoc:\n{}\nerrors:\n{}", e, + mutation, doc.to_string(), errors.errors ); } Error::ExecutableReparse { + mutation, schema, doc, errors, } => { println!( - "{}\ndoc:\n{}\nschema:\n{}\nerrors:\n{}", + "{}\nmutation:\n{}\ndoc:\n{}\nschema:\n{}\nerrors:\n{}", e, + mutation, doc.to_string(), schema.to_string(), errors.errors ); } Error::ExecutableDocumentValidation { + mutation, doc, schema, errors, } => { println!( - "{}\ndoc\n{}\nschema:\n{}\nerrors:\n{}", + "{}\nmutation:\n{}\ndoc\n{}\nschema:\n{}\nerrors:\n{}", e, + mutation, doc.to_string(), schema.to_string(), errors.errors From 26efcb79eb8a3823f98c14d7c52ffd65ddbd4446 Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Sun, 17 Mar 2024 22:17:32 +0000 Subject: [PATCH 18/23] temp --- crates/apollo-smith/src/next/unstructured.rs | 36 +++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index 5b34f0330..6e0031e32 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -1,3 +1,4 @@ +use std::collections::{HashMap, HashSet}; use std::ops::{Deref, DerefMut}; use arbitrary::Result; @@ -142,21 +143,18 @@ impl Unstructured<'_> { &mut self, schema: &Schema, ) -> Result { - let implements = schema.sample_interface_types(self)?; - let implements_fields = Self::all_fields_from_interfaces(&implements); - println!("implements: {:?}", implements); - println!("implements_fields: {:?}", implements_fields); + let implements = Self::all_transitive_interfaces(schema.sample_interface_types(self)?); + let implements_fields = Self::all_unique_fields_from_interfaces(&implements); let new_fields = self.arbitrary_vec(1, 5, |u| { Ok(Node::new(u.arbitrary_field_definition( schema, - DirectiveLocation::InputFieldDefinition, + DirectiveLocation::FieldDefinition, )?)) })?; Ok(ObjectTypeDefinition { description: self.arbitrary_optional(|u| u.arbitrary_node_str())?, name: self.unique_name(), - implements_interfaces: schema - .sample_interface_types(self)? + implements_interfaces: implements .iter() .map(|i| i.name.clone()) .collect(), @@ -229,7 +227,7 @@ impl Unstructured<'_> { directives: schema .sample_directives(self)? .into_iter() - .with_location(DirectiveLocation::InputFieldDefinition) + .with_location(DirectiveLocation::ArgumentDefinition) .try_collect(self, schema)?, }) } @@ -285,15 +283,15 @@ impl Unstructured<'_> { schema: &Schema, ) -> Result { // All interfaces need to have all the fields from the interfaces they implement. - let implements = schema.sample_interface_types(self)?; - let implements_fields = Self::all_fields_from_interfaces(&implements); - println!("implements: {:?}", implements); - println!("implements_fields: {:?}", implements_fields); + let implements = Self::all_transitive_interfaces(schema.sample_interface_types(self)?); + + // Interfaces cannot have duplicate fields so stash them in a map + let mut implements_fields = Self::all_unique_fields_from_interfaces(&implements); let new_fields = self.arbitrary_vec(1, 5, |u| { Ok(Node::new(u.arbitrary_field_definition( schema, - DirectiveLocation::InputFieldDefinition, + DirectiveLocation::FieldDefinition, )?)) })?; @@ -316,15 +314,15 @@ impl Unstructured<'_> { }) } - fn all_fields_from_interfaces( + fn all_unique_fields_from_interfaces( interfaces: &Vec<&Node>, ) -> Vec> { let all_fields = interfaces .iter() .flat_map(|interface| interface.fields.values()) - .map(|field| field.deref().clone()) - .collect::>(); - all_fields + .map(|field| (field.name.clone(), field.deref().clone())) + .collect::>(); + all_fields.values().cloned().collect() } pub(crate) fn arbitrary_field_definition( @@ -605,6 +603,10 @@ impl Unstructured<'_> { } Ok(args) } + fn all_transitive_interfaces<'a>(interfaces: Vec<&'a Node>, schema: &'a Schema) -> Vec<&'a Node> { + // In graphql interfaces can extend other interfaces, but when using them you need to specify every single one in the entire type hierarchy. + + } } #[derive(Copy, Clone, Eq, PartialEq)] From 3d3b20e07e09e0089ed40d2a7eeed7aca286b69d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Mon, 25 Mar 2024 13:55:04 +0100 Subject: [PATCH 19/23] implement all_transitive_interfaces --- crates/apollo-smith/src/next/unstructured.rs | 32 +++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index 6e0031e32..48e22f8a4 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -143,7 +143,8 @@ impl Unstructured<'_> { &mut self, schema: &Schema, ) -> Result { - let implements = Self::all_transitive_interfaces(schema.sample_interface_types(self)?); + let implements = + Self::all_transitive_interfaces(schema, schema.sample_interface_types(self)?); let implements_fields = Self::all_unique_fields_from_interfaces(&implements); let new_fields = self.arbitrary_vec(1, 5, |u| { Ok(Node::new(u.arbitrary_field_definition( @@ -154,10 +155,7 @@ impl Unstructured<'_> { Ok(ObjectTypeDefinition { description: self.arbitrary_optional(|u| u.arbitrary_node_str())?, name: self.unique_name(), - implements_interfaces: implements - .iter() - .map(|i| i.name.clone()) - .collect(), + implements_interfaces: implements.iter().map(|i| i.name.clone()).collect(), directives: schema .sample_directives(self)? .into_iter() @@ -283,7 +281,8 @@ impl Unstructured<'_> { schema: &Schema, ) -> Result { // All interfaces need to have all the fields from the interfaces they implement. - let implements = Self::all_transitive_interfaces(schema.sample_interface_types(self)?); + let implements = + Self::all_transitive_interfaces(schema, schema.sample_interface_types(self)?); // Interfaces cannot have duplicate fields so stash them in a map let mut implements_fields = Self::all_unique_fields_from_interfaces(&implements); @@ -603,9 +602,26 @@ impl Unstructured<'_> { } Ok(args) } - fn all_transitive_interfaces<'a>(interfaces: Vec<&'a Node>, schema: &'a Schema) -> Vec<&'a Node> { - // In graphql interfaces can extend other interfaces, but when using them you need to specify every single one in the entire type hierarchy. + fn all_transitive_interfaces<'a>( + schema: &'a Schema, + interfaces: Vec<&'a Node>, + ) -> Vec<&'a Node> { + // In graphql interfaces can extend other interfaces, but when using them you need to specify every single one in the entire type hierarchy. + interfaces + .into_iter() + .flat_map(|interface| { + std::iter::once(&interface.name).chain( + interface + .implements_interfaces + .iter() + .map(|component| &component.name), + ) + }) + .collect::>() + .into_iter() + .filter_map(|interface| schema.get_interface(interface)) + .collect() } } From 13ae2c6a1dbab972aeca43083bd9ca3d29b53286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Fri, 29 Mar 2024 13:11:54 +0100 Subject: [PATCH 20/23] Fix generating directives on input field definitions Arguments and input fields are both input values, but the directive locations are still different. --- crates/apollo-smith/src/next/unstructured.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index 48e22f8a4..fd3cea89e 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -176,7 +176,10 @@ impl Unstructured<'_> { description: self.arbitrary_optional(|u| u.arbitrary_node_str())?, name: self.unique_name(), arguments: self.arbitrary_vec(0, 5, |u| { - Ok(Node::new(u.arbitrary_input_value_definition(schema)?)) + Ok(Node::new(u.arbitrary_input_value_definition( + schema, + DirectiveLocation::ArgumentDefinition, + )?)) })?, repeatable: self.arbitrary()?, locations: self.arbitrary_directive_locations()?, @@ -196,7 +199,10 @@ impl Unstructured<'_> { .with_location(DirectiveLocation::InputObject) .try_collect(self, schema)?, fields: self.arbitrary_vec(0, 5, |u| { - Ok(Node::new(u.arbitrary_input_value_definition(schema)?)) + Ok(Node::new(u.arbitrary_input_value_definition( + schema, + DirectiveLocation::InputFieldDefinition, + )?)) })?, }) } @@ -204,6 +210,7 @@ impl Unstructured<'_> { pub(crate) fn arbitrary_input_value_definition( &mut self, schema: &Schema, + location: DirectiveLocation, ) -> Result { let ty = schema .random_type( @@ -225,7 +232,7 @@ impl Unstructured<'_> { directives: schema .sample_directives(self)? .into_iter() - .with_location(DirectiveLocation::ArgumentDefinition) + .with_location(location) .try_collect(self, schema)?, }) } @@ -333,7 +340,10 @@ impl Unstructured<'_> { description: self.arbitrary_optional(|u| u.arbitrary_node_str())?, name: self.unique_name(), arguments: self.arbitrary_vec(0, 5, |u| { - Ok(Node::new(u.arbitrary_input_value_definition(schema)?)) + Ok(Node::new(u.arbitrary_input_value_definition( + schema, + DirectiveLocation::ArgumentDefinition, + )?)) })?, ty: schema .random_type( From e62164b39ed1bda23ae8421e3dd97ceb2a86d930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Fri, 29 Mar 2024 16:25:32 +0100 Subject: [PATCH 21/23] Generate enum types --- crates/apollo-smith/src/next/mutations/mod.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/crates/apollo-smith/src/next/mutations/mod.rs b/crates/apollo-smith/src/next/mutations/mod.rs index b212a808b..c4815d75b 100644 --- a/crates/apollo-smith/src/next/mutations/mod.rs +++ b/crates/apollo-smith/src/next/mutations/mod.rs @@ -2,30 +2,31 @@ use std::any::type_name; use apollo_compiler::ast::Document; use apollo_compiler::{ExecutableDocument, Schema}; -use crate::next::mutations::add_anonymous_operation_definition::AddAnonymousOperationDefinition; +use crate::next::mutations::add_anonymous_operation_definition::AddAnonymousOperationDefinition; use crate::next::mutations::add_directive_definition::AddDirectiveDefinition; +use crate::next::mutations::add_enum_type_definition::AddEnumTypeDefinition; use crate::next::mutations::add_input_object_type_definition::AddInputObjectTypeDefinition; use crate::next::mutations::add_interface_type_definition::AddInterfaceTypeDefinition; -use crate::next::mutations::add_object_type_definition::AddObjectTypeDefinition; use crate::next::mutations::add_named_operation_definition::AddNamedOperationDefinition; +use crate::next::mutations::add_object_type_definition::AddObjectTypeDefinition; use crate::next::mutations::add_union_type_definition::AddUnionTypeDefinition; use crate::next::mutations::remove_all_fields::RemoveAllFields; use crate::next::mutations::remove_required_field::RemoveRequiredField; use crate::next::unstructured::Unstructured; +mod add_anonymous_operation_definition; mod add_directive_definition; mod add_enum_type_definition; +mod add_fragment_definiton; mod add_input_object_type_definition; mod add_interface_type_definition; -mod add_object_type_definition; mod add_named_operation_definition; +mod add_object_type_definition; mod add_schema_definition; mod add_union_type_definition; mod remove_all_fields; mod remove_required_field; -mod add_anonymous_operation_definition; -mod add_fragment_definiton; pub(crate) trait SchemaMutation { /// Apply the mutation to the document @@ -43,7 +44,6 @@ pub(crate) trait SchemaMutation { } } - pub(crate) trait ExecutableDocumentMutation { /// Apply the mutation to the document /// Returns false if the mutation did not apply @@ -68,12 +68,15 @@ pub(crate) fn schema_mutations() -> Vec> { Box::new(AddDirectiveDefinition), Box::new(AddInputObjectTypeDefinition), Box::new(AddUnionTypeDefinition), - Box::new(AddInterfaceTypeDefinition), + Box::new(AddEnumTypeDefinition), //Box::new(RemoveAllFields), Box::new(RemoveRequiredField), ] } pub(crate) fn executable_document_mutations() -> Vec> { - vec![Box::new(AddNamedOperationDefinition), Box::new(AddAnonymousOperationDefinition)] + vec![ + Box::new(AddNamedOperationDefinition), + Box::new(AddAnonymousOperationDefinition), + ] } From 0b7286c1b3c5569a245d2069ad28731f48cb63de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Fri, 29 Mar 2024 16:35:12 +0100 Subject: [PATCH 22/23] Generate scalar types --- .../mutations/add_scalar_type_definition.rs | 25 +++++++++++++++++++ crates/apollo-smith/src/next/mutations/mod.rs | 3 +++ crates/apollo-smith/src/next/unstructured.rs | 19 ++++++++++++-- 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 crates/apollo-smith/src/next/mutations/add_scalar_type_definition.rs diff --git a/crates/apollo-smith/src/next/mutations/add_scalar_type_definition.rs b/crates/apollo-smith/src/next/mutations/add_scalar_type_definition.rs new file mode 100644 index 000000000..c1f8713f1 --- /dev/null +++ b/crates/apollo-smith/src/next/mutations/add_scalar_type_definition.rs @@ -0,0 +1,25 @@ +use apollo_compiler::ast::{Definition, Document}; +use apollo_compiler::{Node, Schema}; + +use crate::next::mutations::SchemaMutation; +use crate::next::unstructured::Unstructured; + +pub(crate) struct AddScalarTypeDefinition; +impl SchemaMutation for AddScalarTypeDefinition { + fn apply( + &self, + u: &mut Unstructured, + doc: &mut Document, + schema: &Schema, + ) -> arbitrary::Result { + doc.definitions + .push(Definition::ScalarTypeDefinition(Node::new( + u.arbitrary_scalar_type_definition(schema)?, + ))); + + Ok(true) + } + fn is_valid(&self) -> bool { + true + } +} diff --git a/crates/apollo-smith/src/next/mutations/mod.rs b/crates/apollo-smith/src/next/mutations/mod.rs index c4815d75b..30bb2fc4b 100644 --- a/crates/apollo-smith/src/next/mutations/mod.rs +++ b/crates/apollo-smith/src/next/mutations/mod.rs @@ -10,6 +10,7 @@ use crate::next::mutations::add_input_object_type_definition::AddInputObjectType use crate::next::mutations::add_interface_type_definition::AddInterfaceTypeDefinition; use crate::next::mutations::add_named_operation_definition::AddNamedOperationDefinition; use crate::next::mutations::add_object_type_definition::AddObjectTypeDefinition; +use crate::next::mutations::add_scalar_type_definition::AddScalarTypeDefinition; use crate::next::mutations::add_union_type_definition::AddUnionTypeDefinition; use crate::next::mutations::remove_all_fields::RemoveAllFields; use crate::next::mutations::remove_required_field::RemoveRequiredField; @@ -23,6 +24,7 @@ mod add_input_object_type_definition; mod add_interface_type_definition; mod add_named_operation_definition; mod add_object_type_definition; +mod add_scalar_type_definition; mod add_schema_definition; mod add_union_type_definition; mod remove_all_fields; @@ -69,6 +71,7 @@ pub(crate) fn schema_mutations() -> Vec> { Box::new(AddInputObjectTypeDefinition), Box::new(AddUnionTypeDefinition), Box::new(AddEnumTypeDefinition), + Box::new(AddScalarTypeDefinition), //Box::new(RemoveAllFields), Box::new(RemoveRequiredField), ] diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index fd3cea89e..464124438 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -7,8 +7,8 @@ use apollo_compiler::ast::{ Argument, DirectiveDefinition, DirectiveLocation, EnumTypeDefinition, EnumValueDefinition, Field, FieldDefinition, FragmentDefinition, FragmentSpread, InlineFragment, InputObjectTypeDefinition, InputValueDefinition, InterfaceTypeDefinition, Name, - ObjectTypeDefinition, OperationDefinition, OperationType, SchemaDefinition, Selection, Type, - UnionTypeDefinition, Value, VariableDefinition, + ObjectTypeDefinition, OperationDefinition, OperationType, ScalarTypeDefinition, + SchemaDefinition, Selection, Type, UnionTypeDefinition, Value, VariableDefinition, }; use apollo_compiler::schema::{ExtendedType, InterfaceType}; use apollo_compiler::{ExecutableDocument, Node, NodeStr, Schema}; @@ -237,6 +237,21 @@ impl Unstructured<'_> { }) } + pub(crate) fn arbitrary_scalar_type_definition( + &mut self, + schema: &Schema, + ) -> Result { + Ok(ScalarTypeDefinition { + description: self.arbitrary_optional(|u| u.arbitrary_node_str())?, + name: self.unique_name(), + directives: schema + .sample_directives(self)? + .into_iter() + .with_location(DirectiveLocation::Scalar) + .try_collect(self, schema)?, + }) + } + pub(crate) fn arbitrary_enum_type_definition( &mut self, schema: &Schema, From fcaabb35bec4d20e50fcec74eac78db167ada326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Tue, 9 Apr 2024 11:47:02 +0200 Subject: [PATCH 23/23] Generate completely arbitrary values as input to custom scalars --- crates/apollo-smith/src/next/mod.rs | 14 ++++--- crates/apollo-smith/src/next/unstructured.rs | 43 +++++++++++++++++++- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/crates/apollo-smith/src/next/mod.rs b/crates/apollo-smith/src/next/mod.rs index 1c477ad6d..2bbcfe4d8 100644 --- a/crates/apollo-smith/src/next/mod.rs +++ b/crates/apollo-smith/src/next/mod.rs @@ -7,10 +7,10 @@ use apollo_compiler::{ExecutableDocument, Schema}; pub use crate::next::unstructured::Unstructured; mod ast; +mod executable; mod mutations; mod schema; mod unstructured; -mod executable; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -45,7 +45,7 @@ pub enum Error { ExecutableExpectedValidationFail { schema: Valid, doc: Document, - mutation: String + mutation: String, }, #[error("reparse error")] @@ -86,7 +86,6 @@ pub fn generate_schema_document(u: &mut Unstructured) -> Result continue; } - // Now let's validate that the schema says it's OK match (mutation.is_valid(), new_doc.to_schema_validate()) { (true, Ok(new_schema)) => { @@ -139,7 +138,9 @@ pub(crate) fn generate_executable_document( schema: &Valid, ) -> Result { let mut doc = Document::new(); - let mut executable_document = doc.to_executable(schema).expect("initial document must be valid"); + let mut executable_document = doc + .to_executable(schema) + .expect("initial document must be valid"); let mutations = mutations::executable_document_mutations(); for _ in 0..10 { if u.len() == 0 { @@ -198,7 +199,10 @@ pub(crate) fn generate_executable_document( } } } - println!("Generated executable document: {}\n for schema {}", doc, schema); + println!( + "Generated executable document: {}\n for schema {}", + doc, schema + ); Ok(doc) } diff --git a/crates/apollo-smith/src/next/unstructured.rs b/crates/apollo-smith/src/next/unstructured.rs index 464124438..794277c02 100644 --- a/crates/apollo-smith/src/next/unstructured.rs +++ b/crates/apollo-smith/src/next/unstructured.rs @@ -380,6 +380,43 @@ impl Unstructured<'_> { }) } + pub(crate) fn arbitrary_value_untyped(&mut self) -> Result { + enum ValueKind { + Int, + Float, + Boolean, + String, + Object, + List, + } + let kind = self.choose(&[ + ValueKind::Int, + ValueKind::Float, + ValueKind::Boolean, + ValueKind::String, + ValueKind::Object, + ValueKind::List, + ])?; + + Ok(match *kind { + ValueKind::Int => Value::from(self.arbitrary::()?), + ValueKind::Float => loop { + let val = self.arbitrary::()?; + if val.is_finite() { + return Ok(Value::from(val)); + } + }, + ValueKind::Boolean => Value::Boolean(self.arbitrary()?), + ValueKind::String => Value::String(self.arbitrary_node_str()?), + ValueKind::Object => Value::Object(self.arbitrary_vec(0, 20, |u| { + Ok((u.unique_name(), Node::new(u.arbitrary_value_untyped()?))) + })?), + ValueKind::List => Value::List( + self.arbitrary_vec(0, 20, |u| Ok(Node::new(u.arbitrary_value_untyped()?)))?, + ), + }) + } + pub(crate) fn arbitrary_value(&mut self, schema: &Schema, ty: &Type) -> Result { match ty { Type::Named(ty) => { @@ -412,8 +449,10 @@ impl Unstructured<'_> { } } else if ty.name == "Boolean" { Ok(Value::Boolean(self.arbitrary()?)) - } else { + } else if ty.name == "String" { Ok(Value::String(self.arbitrary_node_str()?)) + } else { + self.arbitrary_value_untyped() } } ExtendedType::Object(ty) => { @@ -485,7 +524,7 @@ impl Unstructured<'_> { .into_iter() .with_location(directive_location) .try_collect(self, schema)?, - selection_set: self.arbitrary_vec(1, 5, |u| { + selection_set: self.arbitrary_vec(1, 20, |u| { Ok(u.arbitrary_selection(schema, object.deref(), executable_document)?) })?, })