#!/usr/bin/python3 """ Python Regression Build Automation Script This Python script serves the purpose of automating nightly regression builds for a software project. The script is designed to handle the setup, execution, and reporting aspects of the regression testing process. Features: 1. Nightly Regression Builds: The script is scheduled to run on a nightly basis, making and executing the regression builds. 2. Markdown Report Generation: Upon completion of the regression tests, the script generates detailed reports in Markdown format. These reports provide comprehensive insights into the test results, including test cases executed, pass/fail status, and any encountered issues. 3. Email Notification: The script is configured to send out email notifications summarizing the regression test results. These emails serve as communication channels for stakeholders, providing them with timely updates on the software's regression status. Usage: - The script is designed to be scheduled and executed automatically on a nightly basis using task scheduling tools such as Cronjobs. To create a cronjob do the following: 1) Open Terminal: Open your terminal application. This is where you'll enter the commands to create and manage cron jobs. 2) Access the Cron Table: Type the following command and press Enter: crontab -e This command opens the crontab file in your default text editor. If it's your first time, you might be prompted to choose a text editor. 3) Edit the Cron Table: The crontab file will open in your text editor. Each line in this file represents a cron job. You can now add your new cron job. 4) Syntax: Our cron job has the following syntax: 0 3 * * * BASH_ENV=~/.bashrc bash -l -c "*WHERE YOUR CVW IS MUST PUT FULL PATH*/cvw/bin/wrapper_nightly-runs.sh > *WHERE YOU WANT TO STORE LOG FILES/cron.log 2>&1" This cronjob sources the .bashrc file and executes the wrapper script as a user. 5) Double check: Execute the following command to see your cronjobs: crontab -l Dependencies: Python: - os - shutil - datetime from datetime - re - markdown - subprocess Bash: - mutt (email sender) Conclusion: In summary, this Python script facilitates the automation of nightly regression builds, providing comprehensive reporting and email notification capabilities to ensure effective communication and monitoring of regression test results. """ import os import shutil from datetime import datetime import re import markdown import subprocess class FolderManager: """A class for managing folders and repository cloning.""" def __init__(self): """ Initialize the FolderManager instance. Args: base_dir (str): The base directory where folders will be managed and repository will be cloned. """ env_extract_var = 'WALLY' print(f"The environemntal variable is {env_extract_var}") self.base_dir = os.environ.get(env_extract_var) print(f"The base directory is: {self.base_dir}") self.base_parent_dir = os.path.dirname(self.base_dir) # print(f"The new WALLY vairable is: {os.environ.get('WALLY')}") # print(f"The Base Directory is now : {self.base_dir}") # print(f"The Base Parent Directory is now : {self.base_parent_dir}") def create_preliminary_folders(self, folders): """ Create preliminary folders if they do not exist. These folders are: nightly-runs/repos/ nightly-runs/results/ Args: folders (list): A list of folder names to be created. Returns: None """ for folder in folders: folder_path = os.path.join(self.base_parent_dir, folder) if not os.path.exists(folder_path): os.makedirs(folder_path) def create_new_folder(self, folders): """ Create a new folder based on the current date if it does not already exist. Args: folder_name (str): The base name for the new folder. Returns: str: The path of the newly created folder if created, None otherwise. """ todays_date = datetime.now().strftime("%Y-%m-%d") return_folder_path = [] for folder in folders: folder_path = os.path.join(self.base_parent_dir, folder, todays_date) if not os.path.exists(folder_path): os.makedirs(folder_path) return_folder_path.append(folder_path) else: return_folder_path.append(None) # Folder already exists return return_folder_path def clone_repository(self, folder, repo_url): """ Clone a repository into the 'cvw' folder if it does not already exist. Args: repo_url (str): The URL of the repository to be cloned. Returns: None """ todays_date = datetime.now().strftime("%Y-%m-%d") repo_folder = os.path.join(self.base_parent_dir, folder, todays_date, 'cvw') tmp_folder = os.path.join(repo_folder, "tmp") # temprorary files will be stored in here if not os.path.exists(repo_folder): os.makedirs(repo_folder) os.system(f"git clone --recurse-submodules {repo_url} {repo_folder}") os.makedirs(tmp_folder) class TestRunner: """A class for making, running, and formatting test results.""" def __init__(self): self.base_dir = os.environ.get('WALLY') self.base_parent_dir = os.path.dirname(self.base_dir) self.current_datetime = datetime.now() #self.temp_dir = self.base_parent_dir #print(f"Base Directory: {self.base_parent_dir}") def copy_setup_script(self, folder): """ Copy the setup script to the destination folder. The setup script will be copied from the base directory to a specific folder structure inside the base directory. Args: folder: the "nightly-runs/repos/" Returns: bool: True if the script is copied successfully, False otherwise. """ # Get today's date in YYYY-MM-DD format todays_date = datetime.now().strftime("%Y-%m-%d") # Define the source and destination paths source_script = os.path.join(self.base_dir, "setup_host.sh") destination_folder = os.path.join(self.base_parent_dir, folder, todays_date, 'cvw') # Check if the source script exists if not os.path.exists(source_script): print(f"Error: Source script '{source_script}' not found.") return False # Check if the destination folder exists, create it if necessary if not os.path.exists(destination_folder): print(f"Error: Destination folder '{destination_folder}' not found.") return False # Copy the script to the destination folder try: shutil.copy(source_script, destination_folder) #print(f"Setup script copied to: {destination_folder}") return True except Exception as e: print(f"Error copying setup script: {e}") return False def set_env_var(self, folder): """ Source a shell script. Args: script_path (str): Path to the script to be sourced. Returns: None """ # find the new repository made todays_date = datetime.now().strftime("%Y-%m-%d") wally_path = os.path.join(self.base_parent_dir, folder, todays_date, 'cvw') # set the WALLY environmental variable to the new repository os.environ["WALLY"] = wally_path self.base_dir = os.environ.get('WALLY') self.base_parent_dir = os.path.dirname(self.base_dir) self.temp_dir = self.base_parent_dir # print(f"The new WALLY vairable is: {os.environ.get('WALLY')}") # print(f"The Base Directory is now : {self.base_dir}") # print(f"The Base Parent Directory is now : {self.base_parent_dir}") def change_time_dur(self, time_duriation=1): # Prepare the command to execute the Makefile make_file_path = os.path.join(self.base_dir, "sim") os.chdir(make_file_path) file_path = "regression-wally" line_number = 450 # TIMEOUT_DUR = 1 day at this line in regression-wally new_line = f" TIMEOUT_DUR = {60*time_duriation}" with open(file_path, 'r') as file: lines = file.readlines() if line_number < 1 or line_number > len(lines): print("Error: Line number out of range.") return False lines[line_number - 1] = new_line + '\n' with open(file_path, 'w') as file: file.writelines(lines) return True def execute_makefile(self, target=None): """ Execute a Makefile with optional target. Args: makefile_path (str): Path to the Makefile. target (str, optional): Target to execute in the Makefile. Returns: True if the tests were made False if the tests didnt pass """ # Prepare the command to execute the Makefile make_file_path = os.path.join(self.base_dir, "sim") os.chdir(make_file_path) output_file = os.path.join(self.base_dir, "tmp", "make_output.log") command = ["make"] # Add target to the command if specified if target: command.append(target) #print(f"The command is: {command}") # Execute the command using subprocess and save the output into a file with open(output_file, "w") as f: formatted_datetime = self.current_datetime.strftime("%Y-%m-%d %H:%M:%S") f.write(formatted_datetime) f.write("\n\n") result = subprocess.run(command, stdout=f, stderr=subprocess.STDOUT, text=True) # Execute the command using a subprocess and not save the output #result = subprocess.run(command, text=True) # Check the result if result.returncode == 0: #print(f"Makefile executed successfully{' with target ' + target if target else ''}.") return True else: #print("Error executing Makefile.") return False def run_tests(self, test_type=None, test_name=None, test_exctention=None): """ Run a script through the terminal and save the output to a file. Args: test_name (str): The test name will allow the function to know what test to execute in the sim directory test_type (str): The type such as python, bash, etc Returns: True and the output file location """ # Prepare the function to execute the simulation test_file_path = os.path.join(self.base_dir, "sim") output_file = os.path.join(self.base_dir, "tmp", f"{test_name}-output.log") os.chdir(test_file_path) if test_exctention: command = [test_type, test_name, test_exctention] else: command = [test_type, test_name] # Execute the command using subprocess and save the output into a file with open(output_file, "w") as f: formatted_datetime = self.current_datetime.strftime("%Y-%m-%d %H:%M:%S") f.write(formatted_datetime) f.write("\n\n") result = subprocess.run(command, stdout=f, stderr=subprocess.STDOUT, text=True) # Check if the command executed successfully if result.returncode or result.returncode == 0: return True, output_file else: print("Error:", result.returncode) return False, output_file def clean_format_output(self, input_file, output_file=None): """ Clean and format the output from tests. Args: input_file (str): Path to the input file with raw test results. output_file (str): Path to the file where cleaned and formatted output will be saved. Returns: None """ # Implement cleaning and formatting logic here # Open up the file with only read permissions with open(input_file, 'r') as input_file: unlceaned_output = input_file.read() # use something like this function to detect pass and fail passed_configs = [] failed_configs = [] lines = unlceaned_output.split('\n') index = 0 while index < len(lines): # Remove ANSI escape codes line = re.sub(r'\x1b\[[0-9;]*[mGK]', '', lines[index]) #print(line) if "Success" in line: passed_configs.append(line.split(':')[0].strip()) elif "passed lint" in line: #print(line) passed_configs.append(line.split(' ')[0].strip()) #passed_configs.append(line) # potentially use a space elif "failed lint" in line: failed_configs.append(line.split(' ')[0].strip(), "no log file") #failed_configs.append(line) elif "Failures detected in output" in line: try: config_name = line.split(':')[0].strip() log_file = os.path.abspath("logs/"+config_name+".log") #print(f"The log file saving to: {log_file} in the current working directory: {os.getcwd()}") failed_configs.append((config_name, log_file)) except: failed_configs.append((config_name, "Log file not found")) index += 1 # alphabetically sort the configurations if len(passed_configs) != 0: passed_configs.sort() if len(failed_configs) != 0: failed_configs.sort() #print(f"The passed configs are: {passed_configs}") #print(f"The failed configs are {failed_configs}") return passed_configs, failed_configs def rewrite_to_markdown(self, test_name, passed_configs, failed_configs): """ Rewrite test results to markdown format. Args: input_file (str): Path to the input file with cleaned and formatted output. markdown_file (str): Path to the markdown file where test results will be saved. Returns: None """ # Implement markdown rewriting logic here timestamp = datetime.now().strftime("%Y-%m-%d") output_directory = os.path.join(self.base_parent_dir, "../../results", timestamp) os.chdir(output_directory) current_directory = os.getcwd() output_file = os.path.join(current_directory, f"{test_name}.md") #print("Current directory:", current_directory) #print("Output File:", output_file) with open(output_file, 'w') as md_file: # Title md_file.write(f"\n\n# Regression Test Results - {timestamp}\n\n") #md_file.write(f"\n\n