Skip to content

Commit

Permalink
📝 add basic documentation for test_generator functions
Browse files Browse the repository at this point in the history
  • Loading branch information
lukacan committed Feb 26, 2024
1 parent 1e8608f commit 1e86cc4
Showing 1 changed file with 189 additions and 30 deletions.
219 changes: 189 additions & 30 deletions crates/client/src/test_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,67 @@ pub enum Error {
ParsingCargoTomlDependenciesFailed,
}

/// Constructs a `PathBuf` from a given root and a series of path components.
///
/// This macro simplifies the creation of a `PathBuf` from multiple strings or string slices,
/// starting with a root path and appending each subsequent component in order. It's useful for
/// dynamically constructing file or directory paths in a more readable manner.
///
/// # Syntax
/// `construct_path!(root, component1, component2, ..., componentN)`
///
/// - `root`: The base path from which to start. Can be a `PathBuf` or any type that implements
/// `Into<PathBuf>`, such as a string or string slice.
/// - `component1` to `componentN`: These are the components to be joined to the root path. Each
/// can be any type that implements `Into<PathBuf>`, allowing for flexible path construction.
///
/// # Returns
/// - Returns a `PathBuf` representing the combined path.
///
/// # Examples
/// Basic usage:
///
/// ```
/// # #[macro_use] extern crate trdelnik_client;
/// # fn main() {
/// use std::path::PathBuf;
///
/// // Constructs a PathBuf from a series of string slices
/// let path = construct_path!(PathBuf::from("/tmp"), "my_project", "src", "main.rs");
/// assert_eq!(path, PathBuf::from("/tmp/my_project/src/main.rs"));
/// # }
/// ```
///
/// Note: Replace `your_crate_name` with the name of your crate where this macro is defined.

#[macro_export]
macro_rules! construct_path {
($root:expr, $($component:expr),*) => {
{
let mut path = $root.to_owned();
$(path = path.join($component);)*
path
}
};
}

/// Represents a generator for creating tests.
///
/// This struct is designed to hold all necessary information for generating
/// test cases for a project. It includes paths to project components,
/// interface definitions, and additional configuration for code generation.
///
/// # Fields
/// - `root`: A `PathBuf` indicating the root directory of the project for which tests are being generated.
/// This path is used as a base for any relative paths within the project.
/// - `idl`: An `Idl` struct. This field is used to understand the interfaces
/// and data structures that tests may need to interact with.
/// - `codes_libs_pairs`: A vector of tuples, each containing a `String` and a `Utf8PathBuf`.
/// Each tuple represents a pair of code and the package path associated with it.
/// - `packages`: A vector of `Package` structs, representing the different packages
/// that make up the project.
/// - `use_tokens`: A vector of `ItemUse` tokens from the Rust syntax, representing `use` statements that
/// should be included in the generated code for .program_client.
pub struct TestGenerator {
pub root: PathBuf,
pub idl: Idl,
Expand All @@ -53,16 +114,6 @@ impl Default for TestGenerator {
}
}

macro_rules! construct_path {
($root:expr, $($component:expr),*) => {
{
let mut path = $root.to_owned();
$(path = path.join($component);)*
path
}
};
}

impl TestGenerator {
/// Creates a new instance of `TestGenerator` with default values.
///
Expand Down Expand Up @@ -96,8 +147,7 @@ impl TestGenerator {
use_tokens: Vec::default(),
}
}

/// Generates both proof of concept (POC) and fuzz tests along with necessary scaffolding.
/// Generates both proof of concept (POC) and fuzz tests along with the necessary setup.
#[throws]
pub async fn generate_both(&mut self) {
let root_path = self.root.to_str().unwrap().to_string();
Expand Down Expand Up @@ -158,6 +208,11 @@ impl TestGenerator {
self.create_trdelnik_manifest().await?;
}

/// Adds new fuzz test. This means create new directory within the
/// trdelnik-tests/fuzz_tests directory, generate necessary files
/// for fuzzing (instructions and snapshots) and modify
/// trdelnik-tests/fuzz_tests/Cargo.toml with the new generated
/// fuzz test binary.
#[throws]
pub async fn add_fuzz_test(&mut self) {
let root_path = self.root.to_str().unwrap().to_string();
Expand All @@ -173,6 +228,9 @@ impl TestGenerator {
// update gitignore
self.update_gitignore(CARGO_TARGET_DIR_DEFAULT)?;
}
/// Performs anchor build command and modify .program_client
/// folder based on the updated program contents. If the .program_client
/// is not yet generated, this will also generate the crate.
#[throws]
pub async fn build(&mut self) {
let root_path = self.root.to_str().unwrap().to_string();
Expand All @@ -186,18 +244,23 @@ impl TestGenerator {
// add/update program_client
self.add_program_client().await?;
}
/// Gathers and expands program data necessary for generating tests.
/// Collect program packages within the programs folder.
/// Call rustc +nightly command in order to expand program macros, then parse
/// the expanded code and obtain necessary data for generating test files
#[throws]
async fn expand_programs(&mut self) {
self.packages = Commander::collect_program_packages().await?;
(self.idl, self.codes_libs_pairs) =
Commander::expand_program_packages(&self.packages).await?;
}
/// Expand .program_client source code and obtain its use statements.
#[throws]
async fn get_program_client_imports(&mut self) {
self.use_tokens = Commander::get_program_client_imports().await?;
}

/// Checks if the whole folder structure for .program_client is already
/// present, if not create/update .program_client crate with the necessary files.
#[throws]
async fn init_program_client(&mut self) {
let cargo_path = construct_path!(self.root, PROGRAM_CLIENT_DIRECTORY, CARGO_TOML);
Expand All @@ -212,9 +275,9 @@ impl TestGenerator {
}
}

// This function checks if whole Fuzz Test folder structer is already initialized,
// Checks if whole Fuzz Test folder structer is already initialized,
// and if fuzz_tests directory contains anything except Cargo.toml and fuzzing folder
// if so we do not proceed with PoC inicialization
// if so the function does not proceed with Fuzz inicialization
#[throws]
async fn init_fuzz_tests(&mut self) {
// create reuqired paths
Expand Down Expand Up @@ -247,8 +310,8 @@ impl TestGenerator {
}
}

// This function checks if whole PoC Test folder structer is already initialized, if so
// we do not proceed with PoC inicialization
// Checks if whole PoC Test folder structer is already initialized, if so
// the function does not proceed with PoC inicialization
#[throws]
async fn init_poc_tests(&mut self) {
// create reuqired paths
Expand All @@ -271,7 +334,13 @@ impl TestGenerator {
}
}

/// Adds a new proof of concept (POC) test to the test workspace.
/// Adds new PoC Test (This will Generate only one PoC Test file).
/// If not present create trdelnik-tests directory.
/// If not present create poc_tests directory.
/// If not present create tests directory.
/// If not present generate PoC test file.
/// If not present add program dependencies into the Cargo.toml file inside poc_tests folder
/// If not present add poc_tests into the workspace virtual manifest as member
#[throws]
async fn add_new_poc_test(&self) {
let program_name = if !&self.idl.programs.is_empty() {
Expand Down Expand Up @@ -308,7 +377,15 @@ impl TestGenerator {
self.add_program_dependencies(&poc_dir_path, "dev-dependencies", None)
.await?;
}
/// Creates the `trdelnik-tests` workspace with `src/bin` directory and empty `fuzz_target.rs` file

/// Adds new Fuzz Test.
/// If not present create trdelnik-tests directory.
/// If not present create fuzz_tests directory.
/// Obtain name for the new fuzz test and generate new fuzz test
/// directory inside fuzz_tests folder.
/// Generate fuzz test files and update Cargo.toml with the new Fuzz Test binary path.
/// If not present add program dependencies into the Cargo.toml file inside fuzz_tests folder
/// If not present add fuzz_tests into the workspace virtual manifest as member
#[throws]
pub async fn add_new_fuzz_test(&self) {
let program_name = if !&self.idl.programs.is_empty() {
Expand Down Expand Up @@ -409,7 +486,14 @@ impl TestGenerator {
.await?;
}

/// ## Creates program client folder and generates source code
/// Add/Update .program_client
/// If not present create .program_client directory.
/// If not present create src directory.
/// If not present create Cargo.toml file
/// If not present add program dependencies into the Cargo.toml file inside .program_client folder
/// Generate .program_client code.
/// If not present add .program_client code
/// If present update .program_client code
#[throws]
async fn add_program_client(&self) {
let cargo_path = construct_path!(self.root, PROGRAM_CLIENT_DIRECTORY, CARGO_TOML);
Expand Down Expand Up @@ -439,14 +523,34 @@ impl TestGenerator {
}
}

/// Creates the `Trdelnik.toml` file
/// If not present create Trdelnik manifest with the templte.
#[throws]
async fn create_trdelnik_manifest(&self) {
let trdelnik_toml_path = construct_path!(self.root, TRDELNIK_TOML);
let trdelnik_toml_content = load_template("/src/templates/Trdelnik.toml.tmpl")?;
self.create_file(&trdelnik_toml_path, &trdelnik_toml_content)
.await?;
}
/// Adds a new member to the Cargo workspace manifest (`Cargo.toml`).
///
/// This function updates the `Cargo.toml` file located at the root of the Cargo workspace
/// by adding a new member to the `members` array within the `[workspace]` table. If the specified member
/// already exists in the `members` array, the function will skip the addition and print a message indicating
/// that the member is already present. Otherwise, it will add the new member and print a success message.
///
/// # Parameters
/// - `&self`: A reference to the current instance of the TestGenerator struct that holds the workspace root path.
/// - `member`: A string slice (`&str`) representing the path to the new member to be added. This path should be
/// relative to the workspace root.
///
/// # Errors
/// Returns an error if:
/// - The `Cargo.toml` file cannot be found, read, or is not properly formatted.
/// - The `Cargo.toml` file does not contain a `[workspace]` table or a `members` array within that table,
/// and it cannot be created.
///
/// The function uses `Error::CannotParseCargoToml` to indicate failures related to parsing or updating the
/// `Cargo.toml` file.
#[throws]
async fn add_workspace_member(&self, member: &str) {
let cargo = construct_path!(self.root, CARGO_TOML);
Expand Down Expand Up @@ -477,7 +581,8 @@ impl TestGenerator {
};
}

/// ## Creates a new directory and all missing parent directories on the specified path
/// If not present creates a new directory and all missing
/// parent directories on the specified path
#[throws]
async fn create_directory_all(&self, path: &PathBuf) {
match path.exists() {
Expand All @@ -487,7 +592,7 @@ impl TestGenerator {
}
};
}
/// ## Creates directory with specified path
/// If not present creates directory with specified path
#[throws]
async fn create_directory(&self, path: &PathBuf) {
match path.exists() {
Expand All @@ -497,8 +602,8 @@ impl TestGenerator {
}
};
}
/// ## Creates a new file with a given content on the specified path
/// - Skip if file already exists
/// If not present creates a new file with a given content on the specified path
/// If file is present, skip
#[throws]
async fn create_file(&self, path: &PathBuf, content: &str) {
let file = path.strip_prefix(&self.root).unwrap().to_str().unwrap();
Expand All @@ -513,8 +618,8 @@ impl TestGenerator {
}
};
}
/// ## Updates a file with a given content on the specified path
/// - Skip if file does not exists
/// If present update a new file with a given content on the specified path
/// If file is not present, skip
#[throws]
async fn update_file(&self, path: &PathBuf, content: &str) {
let file = path.strip_prefix(&self.root).unwrap().to_str().unwrap();
Expand All @@ -530,7 +635,22 @@ impl TestGenerator {
};
}

/// ## Updates .gitignore file in the `root` directory and appends `ignored_path` to the end of the file
/// Updates the `.gitignore` file by appending a specified path to ignore.
///
/// This function checks if the given `ignored_path` is already listed in the `.gitignore` file at the root
/// of the repository. If the path is not found, it appends the `ignored_path` to the file, ensuring that it
/// is ignored by Git. If the `.gitignore` file does not exist or the path is already included, the function
/// will skip the addition and print a message.
///
/// # Parameters
/// - `&self`: A reference to the current instance of the TestGenerator that holds the repository root path.
/// - `ignored_path`: A string slice (`&str`) representing the path to be ignored by Git. This path should be
/// relative to the repository root.
///
/// # Errors
/// Returns an error if:
/// - The `.gitignore` file exists but cannot be opened or read.
/// - There is an error writing the new `ignored_path` to the `.gitignore` file.
#[throws]
fn update_gitignore(&self, ignored_path: &str) {
let gitignore_path = construct_path!(self.root, GIT_IGNORE);
Expand Down Expand Up @@ -559,6 +679,25 @@ impl TestGenerator {
println!("\x1b[93mSkipping\x1b[0m: {GIT_IGNORE}, not found.")
}
}
/// Adds a new binary target to a Cargo.toml file.
///
/// This function reads the existing `Cargo.toml` file from the specified path, adds a new binary target
/// configuration to it, and writes the updated content back to the file. It handles the creation of a new
/// `[[bin]]` section if one does not already exist or appends the new binary target to the existing `[[bin]]`
/// array. The new binary target is specified by its name and the path to its source file.
///
/// # Parameters
/// - `&self`: A reference to the current instance of the TestGenerator, not used directly in this function but
/// necessary for method calls on the instance.
/// - `cargo_path`: A reference to a `PathBuf` that specifies the path to the `Cargo.toml` file to be updated.
/// - `name`: A string slice (`&str`) representing the name of the binary target to be added.
/// - `path`: A string slice (`&str`) representing the path to the source file of the binary target, relative
/// to the Cargo package's root.
///
/// # Errors
/// This function returns an error if:
/// - The `Cargo.toml` file cannot be read or written to.
/// - The content of `Cargo.toml` cannot be parsed into a `toml::Value` or manipulated as expected.
#[throws]
async fn add_bin_target(&self, cargo_path: &PathBuf, name: &str, path: &str) {
// Read the existing Cargo.toml file
Expand Down Expand Up @@ -587,8 +726,28 @@ impl TestGenerator {
// Write the updated Cargo.toml file
fs::write(cargo_path, cargo_toml.to_string()).await?;
}
/// ## Adds program dependency to specified Cargo.toml
/// - for example, we need to use program entry within the fuzzer
/// Adds program dependencies to a specified Cargo.toml file.
///
/// This function updates the Cargo.toml file located in the given directory by adding new dependencies
/// specified by the `deps` parameter. It supports adding dependencies with or without features. The
/// dependencies are added based on the packages found in the `self.packages` collection of the TestGenerator,
/// where each package's path is adjusted to be relative to the specified `cargo_dir`. If no packages are
/// found in `self.packages`, the function will return an error.
///
/// # Parameters
/// - `&self`: A reference to the current instance of the TestGenerator, which contains a collection of packages
/// to be added as dependencies.
/// - `cargo_dir`: A reference to a `PathBuf` indicating the directory where the `Cargo.toml` file to be updated is located.
/// - `deps`: A string slice (`&str`) specifying the section under which the dependencies should be added
/// (e.g., `dependencies`, `dev-dependencies`, etc.).
/// - `features`: An optional vector of string slices (`Vec<&str>`) specifying the features that should be
/// enabled for the dependencies being added. If `None`, no features are specified.
///
/// # Errors
/// This function can return errors in several cases, including:
/// - If the specified `Cargo.toml` file cannot be read or written to.
/// - If parsing of the `Cargo.toml` file or the dependencies fails.
/// - If no packages are found in `self.packages`.
#[throws]
async fn add_program_dependencies(
&self,
Expand Down

0 comments on commit 1e86cc4

Please sign in to comment.